[BUG] --worktree runs WorktreeCreate for --version/--help and on failed --tmux attach, never calls WorktreeRemove (leaks worktrees + tmux sessions)
Summary
With --worktree, Claude Code runs the WorktreeCreate hook before it decides what the invocation will do. Early-exit flags (--version, --help) and a failed --tmux attach then exit without ever calling WorktreeRemove, so every such invocation leaks a worktree. Combined with --tmux it also leaks a tmux session with an idle interactive claude inside.
Environment
- Claude Code 2.1.293, macOS (Darwin 27.0.0), Ghostty, zsh
- Worktrees provided by hooks (jj workspaces, no
.gitin the worktree):
"hooks": {
"WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "claude-worktree-create" }] }],
"WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "claude-worktree-remove" }] }]
}
# claude-worktree-create
dest="$(jj workspace root)/.claude/worktrees/$(jq -r .name)"
mkdir -p "$dest"
jj workspace add "$dest" >&2
echo "$dest"
# claude-worktree-remove
jj workspace remove "$(jq -r '.worktree_path | split("/") | last')" >&2
Steps to reproduce
- In a repo with the hooks above, from a shell without a tty (e.g. a subprocess, CI, or a Claude Code Bash tool call):
````
claude --worktree --tmux --version
Output:
````
Using worktree via hook: /Users/arian/Projects/nixos-stuff/.claude/worktrees/bright-fox-xpaa
open terminal failed: not a terminal
The worktree directory stays behind. The remove hook never runs.
- Same with
--help: the worktree is created, help is printed, nothing is cleaned up.
- From a real tty,
claude --worktree --tmux --versioncreates the worktree, creates a tmux session named<repo>_worktree-<name>, and starts a full interactive session inside it.--versionis effectively ignored, and the tmux session plus worktree stay alive after the calling shell moves on.
Evidence
jj op log after six such invocations contains only add workspace operations and not a single removal:
17:14:15 add workspace 'bright-ray-2zot'
17:12:56 add workspace 'calm-ray-tjke'
17:12:45 add workspace 'calm-ray-cgeh'
17:12:35 add workspace 'bright-oak-knl6'
17:09:45 add workspace 'bright-elm-0fcc'
17:09:43 add workspace 'bright-fox-xpaa'
tmux ls afterwards:
nixos-stuff_worktree-calm-ray-cgeh: 1 windows (attached)
nixos-stuff_worktree-calm-ray-tjke: 1 windows (attached)
All leaked worktrees are empty (unchanged from the base commit), so the documented "unchanged worktrees are removed automatically on exit" behaviour should have applied if the exit path had reached it.
Expected
--versionand--helpshort-circuit before any worktree or tmux setup, as they do for every other side-effecting flag.- If the tmux attach fails, the just-created (unchanged) worktree is rolled back via
WorktreeRemove. - More generally, any exit after
WorktreeCreatehas run should go through the same cleanup as a normal session exit.
Why it matters
The natural way to make worktree+tmux the default is alias claude='claude --worktree --tmux' (there is no settings key for it). With that alias, every incidental claude --version, claude -p ..., claude mcp ... or claude update leaks a worktree, and a Claude Code session whose Bash tool inherits the alias will leak one per probe without anyone noticing.
Related
- #93676 and #99270 cover other paths where
WorktreeRemovenever fires. - #94212 / #92342 ask for cleanup events in general.
- A
settings.jsonoption to default--worktree/--tmuxfor interactive sessions would remove the need for the alias entirely.