Build1 publisher3 min readPublished
A git worktree lock you set yourself survives Claude Code's exit cleanup
One developer's test on Claude Code 2.1.263 lost a clean worktree and its branch at /exit with no prompt. The cleanup routine he extracted only releases locks whose reason it wrote itself, so a lock set by hand holds.
The Engineer · Build desk

What happened
- A `claude --worktree` session with no uncommitted changes and no new commits removes the worktree and its branch at /exit, with no Keep/Remove dialog, according to the developer who tested it.
- The silent path runs three commands in sequence: `git worktree unlock`, then `git worktree remove --force`, then `git branch -D` against the branch `worktree-<name>`.
- Re-locking the worktree with a reason of your own makes the cleanup back off, and after /exit both the worktree and its branch are still listed, marked locked.
- A SessionStart hook can do the re-lock on its own, and the script in the post exits immediately when it finds itself running in the main worktree instead of a linked one.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- contradiction The documentation says a named session prompts before a clean worktree goes, and the reproduction passed a name and got no prompt, so reading the page does not tell a team whether its sessions delete.
- exposure The people with the most to lose are the ones who pre-create a worktree and commit before attaching an agent, because the counter only sees commits made after the session began.
- decision Every team running agent sessions in linked worktrees now has to decide whether claiming a lock is the first action of every session, since the protection has to exist before the first exit.
- constraint Protection lives in git's per-worktree lock file, so nothing in a repo checkout or a settings file carries it across, and each worktree has to be claimed one at a time.
Claude Code writes a lock on every worktree it creates. The registry shows the reason in full: `claude session wt-lock (pid 72411 start Tue Sep 8 13:50:41 2026)` [10]. At exit the cleanup routine reads that reason back and tests it against a regex the author pulled out of the bundled source, `/^claude (?:agent|session) .{1,255} \(pid (\d{1,10})(?: start (.{1,255}))?\)$/` [11]. A reason that fails the match, or one that matches but carries the PID of another live process, sends the routine down the keep path, where it logs that the worktree is "locked by another live Claude Code process, or with a reason we did not write" [12].
Git forces the order of the workaround. You cannot stack a second lock, so `git worktree lock --reason "keep"` fails while Claude Code's own lock is in place, and you unlock first [14]. The text you pick does not matter. Running `git worktree unlock . && git worktree lock .` from inside the worktree, with no reason at all, also kept it [15]. Coming back later with `claude --worktree <name>` drops you into the same worktree with your lock still on it, so it survives the following exit too [16].
Anthropic's worktrees page describes a different exit. "The worktree is clean: for an unnamed session, Claude removes the worktree and its branch automatically. A named session prompts you first so you can keep the worktree for later," the page says [9]. The reproduction in the post passed a name, `claude --worktree wt-clean`, and no prompt appeared [1]. The author calls the deletion documented behavior and not a bug, and reports no `worktree.keepOnExit`-style setting anywhere [2][19]. The post does not reconcile the prompt language with what it observed.
The commit count is where this gets expensive. The exit path runs `git rev-list --count <HEAD at session start>..HEAD`, so only commits made during the session raise the number [5][7]. Pre-create a worktree with `git worktree add`, commit a day of work, then attach a session to it, and the count comes back zero. If the branch is named `worktree-<name>`, the cleanup's `git branch -D` takes the unmerged commits with it [8]. The only notice is a line reading `Cleaning up worktree (no pending changes)`, which the post says you see if you do not blink [4].
For the lock trick to transfer, the build has to behave like the one under test: Claude Code 2.1.263, git 2.52.0, macOS [3]. The keep path, the regex and the log string all come from one binary's bundled source [5]. The documented commitment is narrower, and it covers the background stale-lock sweep: the docs say the sweep never releases a lock you set yourself with `git worktree lock` [13]. Three GitHub issues about the auto-cleanup are closed, among them #46444, titled "worktree auto-cleanup permanently deleted 10 days of uncommitted project work without any warning" [18].
What to watch
- Whether a build after 2.1.263 adds a keep-on-exit setting, or changes how the exit cleanup treats locks it did not write.
- Whether the worktrees page is corrected on named sessions prompting, or the prompt reappears in the client.
- Whether the unlock and re-lock behaves the same on Linux and Windows; the reported test was macOS only.