Where state lives
Everything gitq knows lives in plain JSON files you can read, diff, and delete. Nothing is written into your working tree, nothing is hidden in a database, and no file is required for gitq to start. This page lists every location, what it holds, when it is written, and what happens if you throw it away.
Every write is atomic: gitq writes a temp file and renames it over the target. The stack store, the operation log, and the lease registry additionally take a file lock across the read-modify-write, so two cascades finishing at the same moment cannot lose each other's changes.
The short version
| Path | Holds | Safe to delete |
|---|---|---|
<app root>/stacks/<hash>.json | One repo's tracked stacks | Yes. You lose the tree, not the branches. |
<app root>/settings.json | maxWorkSlots, workSlotLocation, forges -- the fallback for gitq.workSlots/gitq.forges in the settings store, until each is imported | Yes. Falls back to the defaults. |
<app root>/operation-log.json | The last 50 operations, all repos | Yes. log goes empty, undo has nothing to do. |
<commonDir>/gitq/leases.json | Which stack holds which work slot | Only when nothing is running or parked. |
<gitdir>/gitq-pause.json | A paused cascade's position | Only after aborting the rebase it describes. |
<app root>/state/jobs/*.json | Board job status | Yes. |
<app root>/config.json | Board configuration -- the fallback for gitq.board in the settings store, until it's imported | It is yours to manage. |
The app root, and the config directory inside it
Everything gitq owns hangs off one root: ~/.mattstack/gitq, whether gitq runs from a checkout or as the bundled binary. GITQ_APP_ROOT moves that root. Inside it sit the stack stores, settings.json, the operation log, the work slots gitq creates, and the board's config.json and state/jobs/.
GITQ_CONFIG_DIR still moves just the CLI's own subset -- stacks, settings, operation log, work slots -- and wins over the app root when both are set. That is the throwaway-store escape hatch; the board's files stay in the app root regardless. Both variables are read once when gitq starts, so export them in your shell profile rather than expecting a mid-session change to take effect, and remember that a gitq run with a different GITQ_CONFIG_DIR sees a different set of tracked stacks.
The work slots gitq leases for cascades follow the config directory with no carve-out: a non-pool repo's slots are created under <config dir>/work/<hash>/ (getWorkSlotRoot, src/core/config-paths.ts).
Moving from ~/.config/gitq
gitq used to keep these files in ~/.config/gitq, and its work slots in ~/.cache/gitq/work. The first run after the move copies ~/.config/gitq into the app root and records where it came from in migrated-from.json. It copies: the old directory is left exactly as it was, for you to delete once you are satisfied. A second run is a no-op. If both directories already hold files and no marker connects them, gitq uses the app root and says once which other directory it is ignoring -- it will not merge two stack stores, because nothing tells it which one is current.
Work slots need no migration at all. A slot is recognised by its gitq-<n> directory name in the repo's own git worktree list, never by where it sits, and leases record absolute paths, so slots already leased under ~/.cache/gitq/work keep working exactly as before. Only newly created slots land under the new root.
So a throwaway GITQ_CONFIG_DIR moves the git worktrees gitq creates, not just the files listed here. It does not make the run invisible to the repo you point it at: the lease registry, a paused cascade's file, and the per-worktree core.hooksPath are written into that repo's git directory whatever the variable says. Those are the repo-side files below.
stacks/<hash>.json
One file per repo, holding that repo's entire stack tree: repoPath, remoteUrl, commonDir, and the stacks array.
The <hash> is derived from the repo's identity, not from the directory you ran in. Identity means the real path of the repo's git common dir, the shared .git that every worktree of a repo points at, and the hash is the first 16 hex characters of its SHA-256. That is the mechanism behind gitq being worktree-native: run gitq from the primary checkout, from a sibling worktree, or from a work slot, and all three resolve the same file and see the same stacks.
Written by every command that changes the tree: track, untrack, add, remove, all of the surgery commands, sync (to record each branch's new head), publish (to record MR ids and URLs), and import.
Deleting it forgets the tree. It does not delete, move, or otherwise disturb a single branch. Rebuild by re-running gitq track and gitq add, or by importing the stack back from its merge request chain with gitq import.
gitq used to key this file by a single worktree's path. The first time you run gitq from that worktree, the legacy file is migrated to the identity-keyed name and the original is renamed to .bak. If an identity-keyed store already exists, the legacy stacks are merged into it, skipping any whose name is already taken, and gitq prints a line to stderr saying so. Nothing is deleted either way.
settings.json
The keys gitq reads from it are maxWorkSlots, workSlotLocation, and forges -- each also readable, and once imported overridable, through the settings store:
{ "maxWorkSlots": 5, "workSlotLocation": "root" }
maxWorkSlots must be a number of at least 1. Anything else, or nothing in the store or this file, gives the default of 3. workSlotLocation is auto (the default: slots join a pool's worktrees) or root (slots always go under the work-slot root). gitq never creates or writes this file; make it yourself if you want to change a value without the store. See Work slots and leases and Configuration.
operation-log.json
An array of operation entries, capped at the most recent 50, first in first out. Each entry records the operation type, a timestamp, every git command it ran (with arguments, working directory, exit code, and duration), a snapshot of the stack tree, and branchSnapshots: the head SHA of the root and of every branch in the stack, captured before the operation ran. That snapshot is what gitq undo restores from, and it is why gitq log can tell you what a command actually did.
The file is global across every repo. Each entry is stamped with the repo it ran in, and log and undo filter to the current repo by common dir, so operations in one repo never show up or become undoable in another.
Two details worth knowing:
- An entry is written when the command succeeds, and a cascade that pauses (exit
2) logs nothing, because a pause is resolved withcontinueorabort, not withundo. But "succeeds" is not the same as "exits0" for every command:sync,continue, andreparentlog on any exit code except2, so asyncthat exits1because one branch failed is still logged. That is deliberate. Exit1there means some branches rebased fine and one did not, andundoneeds that entry to rewind the ones that did move; without it, a partially failed sync would leave you with no recorded way back. gitq continuewrites its own entry when the cascade finally completes, and its snapshots were taken whencontinuestarted, not when the originalsyncdid. Undoing a sync that paused part way therefore rewinds to the middle of that cascade, not to before it.
Deleting the file is safe and loses only history.
Inside the repo
<commonDir>/gitq/leases.json
The work-slot lease registry: which stack is using which work slot, under which action, in which state (running or parked). <commonDir> is the repo's shared git dir, so one registry covers every worktree of the repo.
Written whenever a cascade acquires, parks, or releases a lease, always under a file lock. Dead running leases are reaped automatically on the next acquisition; parked ones never are, because a parked lease is a conflict waiting on you.
Deleting it while a cascade is running or parked is how you lose track of a real mid-rebase state: the pause file is still on disk, the rebase is still half-done in the slot, and gitq continue can no longer find it. Clear it the normal way with gitq continue or gitq abort. When nothing is running, the file is inert and safe to remove. See Work slots and leases.
<gitdir>/gitq-pause.json
Present only while a cascade is paused. It records the branch that conflicted, the conflicted files, the branches done and still to do, and enough position information to resume exactly where the walk stopped.
Because a cascade rebases in a leased work slot, <gitdir> is that slot's git dir, not your checkout's, so the file usually sits at <commonDir>/worktrees/gitq-1/gitq-pause.json. It is written before the store is saved, so "a pause file exists if and only if a rebase is in progress" survives a crash between the two writes.
Deleting it by hand leaves the actual mid-rebase state in the slot untouched, which is worse than the pause. Use gitq abort. See The pause protocol and Pause file.
Per-worktree git config in work slots
Not a gitq file, but gitq writes it: the first time a work slot is provisioned, gitq turns on git's per-worktree config extension for the repo and sets core.hooksPath to /dev/null for that slot only. Your own checkouts keep their hooks. The setting is re-applied on every slot acquisition, so it heals itself if it is removed.
The board
Both of these live in the app root and, unlike the CLI's files, do not follow GITQ_CONFIG_DIR: a throwaway stack store should not also repoint the board.
config.json
The board's own configuration: which repos to display, which port to bind, the herdr workspace -- the fallback for gitq.board in the settings store, until that key is imported. Read once at server startup, so an edit (to either the store or the file) needs a restart. Start from config.example.json for the file-only path. See The board and Configuration.
state/jobs/*.json
One file per board job, named deterministically from the repo path, stack name, and action, so relaunching the same action reuses the same file. Each holds the job's status (starting, working, conflict, done, error), a free-text detail the board renders verbatim, the herdr tab and workspace ids, the Claude session id for resuming the conversation, and timestamps.
The agent skill writes these by running <status-bin> job-status -- the gitq executable the board injected -- as it works; the board reads the directory to draw its job chips and activity feed. Entries that reached done or error are pruned after 24 hours. A job parked at a conflict keeps its file however long it takes. Deleting them loses nothing but the board's memory of recent runs.
What gitq reads but never writes
The GitLab token for publish and import: GITLAB_TOKEN from the environment first, then a grant-gated read from the rt daemon (secrets:forge-token, backed by rt's own encrypted store, not a plaintext file). gitq never writes either. See Configuration.
Next
- The stack tree: what is actually in the store file.
- Configuration: every setting, in one place.
- Recover: what to do when these files and git disagree.