ExitWorktree can't remove a worktree launched via --worktree (or entered via path:), so WorktreeRemove hook never fires and per-worktree resources leak
Environment
- Claude Code: 2.1.236
- OS: macOS (Darwin)
- Project has
.claude/settings.jsonwithWorktreeCreateandWorktreeRemovehooks configured, used to provision/tear down per-worktree build caches (XcodeDerivedData) and a cloned iOS Simulator.
Description
WorktreeCreate/WorktreeRemove hooks exist so a project can keep expensive per-worktree side effects (build caches, simulators, secret symlinks, etc.) in sync with the worktree's lifecycle. WorktreeCreate fires correctly when a session is launched via claude --worktree <name> — confirmed directly: the resulting worktree used our custom hook's path/branch convention rather than Claude Code's built-in default (repo root, origin/HEAD), and our ios/.claude project config was active for the whole session, which is only possible if the hook's chosen directory was actually used as the session cwd.
However, there is no corresponding way to trigger WorktreeRemove for that same worktree from inside the session. The ExitWorktree tool only tracks and permits removal of a worktree that was created via an in-chat EnterWorktree(name:) tool call in the same session. It refuses to act on:
- The worktree a session was launched directly into via
claude --worktree <name>, or - A worktree entered mid-session via
EnterWorktree(path: "<existing-worktree>").
In both cases, the only way to actually remove the worktree is to bypass Claude Code entirely (raw git worktree remove, or deleting the directory), which never triggers WorktreeRemove — so any resources that hook was responsible for tearing down (in our case, several GB of Xcode DerivedData plus a cloned iOS Simulator) are silently leaked with no prompt, warning, or automated recovery.
Repro steps (case 1 — CLI-launched)
- In a project with
.claude/settings.jsondefining aWorktreeCreatehook and aWorktreeRemovehook (the latter doing something observable, e.g. writing a marker file or deleting a directory it created). - From that project directory, run
claude --worktree my-feature. - Confirm
WorktreeCreateran (e.g. the marker/artifact it produces exists). - Do some work, then ask Claude mid-chat to "clean up this worktree," or directly invoke the
ExitWorktreetool withaction: "remove". - Observed:
ExitWorktreerefuses / reports there is no activeEnterWorktreesession to exit, even though the running session's cwd is inside the worktree the hook created. - As the only available path, remove the worktree manually (
git worktree remove <path>from elsewhere, or have Claude do it via Bash). - Observed: the
WorktreeRemovehook never runs. Whatever it was responsible for tearing down (build cache, simulator, etc.) is left on disk indefinitely.
Repro steps (case 2 — entered via path:)
- Start a session anywhere in the project.
EnterWorktree(path: "<path to some other pre-existing worktree>").- Later, ask Claude to clean up that worktree, or invoke
ExitWorktree(action: "remove"). - Observed: refused, per the tool's own documented behavior ("
ExitWorktreewill not remove a worktree entered this way; useaction: "keep""). Same downstream leak as above once removal is done manually.
Expected behavior
Since WorktreeCreate/WorktreeRemove are meant to keep a worktree's lifecycle and its side effects in sync, there should be a supported way to trigger WorktreeRemove for any worktree the current session is rooted in or was pointed at — not only ones created via an in-chat EnterWorktree(name:) call. At minimum, ExitWorktree(action: "remove") should work for the worktree a session was launched directly into via --worktree, since that's the most common way these hooks get exercised in the first place.
Actual behavior
ExitWorktree silently limits its removal capability to worktrees it created itself in the same session via EnterWorktree(name:). Any other worktree can only be removed by working around Claude Code entirely, which bypasses WorktreeRemove with no warning that hook-managed resources are about to be orphaned.
Impact
For any project using these hooks to manage non-trivial per-worktree resources, every --worktree-launched session leaks those resources on cleanup unless the user separately remembers to run a project-specific manual GC script (ours: worktree-gc.sh --prune). This defeats the purpose of WorktreeRemove for what is likely the most common way these hooks get invoked.
Additional context
There's a separate, likely-unrelated history of WorktreeCreate hooks causing claude --worktree to hang indefinitely before the hook even runs (#42752, #41614) — last reported reproducing around 2.1.126, both auto-closed by inactivity rather than a confirmed fix. That's a different failure mode (hang vs. silent-skip-on-removal) and isn't what we're reporting here; noting it only in case it's the same root cause resurfacing differently.