AI & ML
Claude Code deletes clean worktrees on exit without asking. git worktree lock stops it.
day DEV Community
7 views
TL;DR
If you /exit a claude --worktree <name> session with no uncommitted changes and no new commits, Claude Code removes the worktree and its branch without showing the Keep/Remove dialog.
This is documented behavior, not a bug. There is no setting to turn it off.
Claude Code holds its own git worktree lock on the worktree and only ever releases locks whose reason matches its own format. Re-lock the worktree with your own reason and the cleanup backs off.
A SessionStart hook can do the re-lock automatically.
Pre-creating the worktree with git worktree add does not protect it. If its branch is named worktree-<name>, existing unmerged commits get force-deleted too.
Tested on Claude Code 2.1.263, git 2.52.0, macOS.
The symptom
You start an isolated session:
claude --worktree wt-clean
You read some code, maybe run a few commands, and then /exit. You never picked "Remove worktree". But:
$ git worktree list
/path/to/demo 731024a [main]
# .claude/worktrees/wt-clean is gone, and so is branch worktree-wt-clean
If you blink you see Cleaning up worktree (no pending changes)… flash by. That's the only notice you get.
What the exit code actually checks
I pulled the bundled source out of the Claude Code binary to see the decision. On /exit from a worktree session it runs two git commands inside the worktree:
git status --porcelain # changed + untracked files
git rev-list --count <HEAD at session start>..HEAD # new commits this session
Then it branches like this:
Worktree state
Session named?
What /exit does
0 changed files, 0 new commits
no
Removes worktree and branch, no prompt
0 changed files, 0 new commits
yes (/rename)
Keep / Remove dialog
Any uncommitted change or any new commit
either
Keep / Remove dialog
The silent path runs:
git worktree unlock <path>
git worktree remove --force <path>
git branch -D worktree-<name>
The "new commits" check is relative to the HEAD at session start. Anything committed before the session doesn't count. Keep that in mind for the pre-created worktree gotcha below.
It's documented
From the official worktrees page:
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
So the design intent is "clean means disposable". Plenty of people disagree. Related issues (all closed):
#27753 Worktree auto-deleted on exit when work is committed
#46444 worktree auto-cleanup permanently deleted 10 days of uncommitted project work without any warning
#58432 WorktreeRemove hook suppresses the /exit keep-or-remove picker
There is no worktree.keepOnExit-style setting. So we lean on git instead.
Why git worktree lock works
Claude Code already uses git worktree lock on every worktree it creates. Look at the registry while a session is running:
$ git worktree list --porcelain
worktree /path/to/demo/.claude/worktrees/wt-lock
HEAD 731024acd920b9883ae2f00845fa4ebc8ae0230e
branch refs/heads/worktree-wt-lock
locked claude session wt-lock (pid 72411 start Tue Sep 8 13:50:41 2026)
At exit, the cleanup routine reads the lock reason back and tests it against this regex (from the bundle):
/^claude (?:agent|session) .{1,255} \(pid (\d{1,10})(?: start (.{1,255}))?\)$/
If the reason doesn't match, or it matches but the PID belongs to another live process, the routine keeps the worktree and logs:
cleanupWorktree: kept <path> — locked by another live Claude Code process, or with a reason we did not write
The docs say the same thing about the background stale-lock sweep: "The sweep never releases a lock you set yourself with git worktree lock." A user-set lock is consistently treated as off-limits.
Fix 1: re-lock by hand
During the session, from another terminal (or with the ! prefix inside Claude Code):
git worktree unlock .claude/worktrees/wt-lock
git worktree lock --reason "pinned by ryan" .claude/worktrees/wt-lock
You must unlock first. Claude Code's lock is already there, so a bare lock fails:
$ git worktree lock --reason "keep" .claude/worktrees/wt-lock
fatal: '.claude/worktrees/wt-lock' is already locked, reason: claude session wt-lock (pid 72411 start ...)
Now /exit:
$ git worktree list
/path/to/demo 731024a [main]
/path/to/demo/.claude/worktrees/wt-lock 731024a [worktree-wt-lock] locked
The reason text doesn't matter. Running git worktree unlock . && git worktree lock . from inside the worktree, with no reason at all, also kept it.
Next time, claude --worktree wt-lock drops you back into the same worktree. Your lock stays, so it survives the next /exit too.
Fix 2: automate it with a SessionStart hook
Save this script:
#!/bin/sh
# .claude/hooks/pin-worktree.sh
# Keep Claude Code from auto-removing this linked worktree on exit.
git_dir=$(git rev-parse --git-dir 2>/dev/null) || exit 0
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
[ "$git_dir" = "$common_dir" ] && exit 0 # main worktree: nothing to pin
wt=$(git rev-parse --show-toplevel)
git worktree unlock "$wt" 2>/dev/null # drop Claude Code's own lock
git worktree lock --reason "pinned by SessionStart hook" "$wt"
In the main worktree --git-dir and --git-common-dir are the same path, so the script is a no-op there. In a linked worktree it swaps the lock.
Register it:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/pin-worktree.sh\"" }
]
}
]
}
}
Right after claude --worktree starts:
$ git worktree list --porcelain | grep locked
locked pinned by SessionStart hook
/exit leaves the worktree in place, still locked.
Where the settings file has to live
This tripped me up, so here's what I measured:
Settings location
Hook fires in the worktree session?
.claude/settings.json committed to the repo
yes (it's in the worktree checkout)
Untracked .claude/settings.local.json only in the main checkout
no (the worktree checkout doesn't have it)
A settings file outside the repo (passed with --settings)
yes
~/.claude/settings.json (user settings)
yes
The worktree session reads project settings from the worktree's own checkout. If you don't want to commit anything, put the hook in your user settings at ~/.claude/settings.json with an absolute script path. The script has no repo-specific logic, so it's safe globally.
Side note from the docs: $CLAUDE_PROJECT_DIR keeps pointing at the main checkout after Claude enters a worktree, while the hook's cwd follows the worktree. That's why the script derives the path from git rev-parse instead of the env var.
Gotchas
Pre-creating the worktree doesn't help
I assumed a worktree I made myself would be left alone. It wasn't. claude --worktree <name> adopts an existing .claude/worktrees/<name> and applies the same exit logic.
Worse: if the branch is named worktree-<name>, commits made before the session don't count as "new", so an unmerged branch gets force-deleted:
$ git worktree add -b worktree-wt-pre4 .claude/worktrees/wt-pre4
$ cd .claude/worktrees/wt-pre4 && echo precious > precious.txt && git add . && git commit -m "precious"
$ claude --worktree wt-pre4 # do nothing, /exit
$ git branch --list worktree-wt-pre4
# empty. No ref points at that commit any more.
The commit object still exists, so git fsck --lost-found can dig it out, but only if you notice. If you hand a hand-made worktree to Claude Code, lock it first.
One-off alternative: name the session
A named session always gets the dialog, even when clean:
Exiting worktree session
This session was named "keepme". Keep the worktree to resume it later, or remove it to clean up.
❯ 1. Keep worktree
2. Remove worktree
/rename keepme then /exit is the cheapest way to keep a worktree once. You have to remember it every time, which is why I prefer the hook.
Cleaning up later
Claude Code won't touch a worktree you locked, so removal is on you:
git worktree unlock .claude/worktrees/wt-lock
git worktree remove .claude/worktrees/wt-lock
git branch -D worktree-wt-lock
Wrap-up
Claude Code's worktree cleanup is aggressive by design: clean plus unnamed equals gone. The escape hatch is already built in, just not advertised. Claude Code refuses to release a git worktree lock it didn't write, so a re-lock with your own reason, done by hand or by a SessionStart hook, is enough to keep the worktree across sessions.
Links:
Claude Code docs, worktrees: https://code.claude.com/docs/en/worktrees
Claude Code docs, hooks: https://code.claude.com/docs/en/hooks
git worktree manual: https://git-scm.com/docs/git-worktree
Read original: https://dev.to/day_b3fa2204948ded3059f6f/claude-code-deletes-clean-worktrees-on-exit-without-asking-git-worktree-lock-stops-it-ej
← Previous
Agentic development needs a famous 5 minute install
Next →
Camino a CI/CD: Pre-Merge Gates: Cómo implementamos Governance en Pull Requests
Related
SEO in 2026: Why Brand Signals and Entity Authority Matter Alongside Backlinks
AI & ML
0
Dev.to (EN Zone)
I don't open a video editor any more. I ask Claude instead.
AI & ML
0
Dev.to (EN Zone)
4,768 LLM Runs, Zero Lost Sweeps: Hardening a Field-Test Runner for Timeouts, Hangs, and Cost
AI & ML
0
Dev.to (EN Zone)
Machine Learning and Its Real-World Impacts
AI & ML
2
DEV Community
Comments0
No comments yet — be the first