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