Stale worktrees are never cleaned up
Status Open
Maintainer reply None cached
Workaround ✓ Mentioned in thread ↓
Activity 9 comments · opened Feb 18, 2026
Problem
Claude Code creates git worktrees under ~/.claude-worktrees/ for parallel task execution but has no mechanism to clean them up if a session ends without proper cleanup (crash, interruption, or bug in teardown). There is also no garbage collection on subsequent session starts — each session is completely unaware of what previous sessions left behind.
This leaves behind:
- Full repo copies on disk (wasting space)
- Git branches that cannot be deleted even with
git branch -Dbecause git refuses to delete a branch checked out in any worktree - Worktree refs in
.git/worktrees/that persist indefinitely
The only way a user discovers these is by stumbling into the cannot delete branch error, at which point they have to manually git worktree remove or rm -rf the directory, then git worktree prune to clean up refs.
Reproduction
- Use Claude Code on a project where it creates a worktree (parallel task execution)
- End the session (or have it crash/interrupt)
- Observe that
~/.claude-worktrees/<project>/<worktree-name>persists - Try to delete the branch:
git branch -D <worktree-name>→ fails witherror: cannot delete branch used by worktree
Expected behavior
- Claude Code should clean up worktrees on session exit
- On session start, Claude Code should scan for and prune stale worktrees from previous sessions
- At minimum, a
claude --cleanupcommand or similar should exist
Environment
- macOS (Darwin 25.2.0)
- Claude Code (CLI)
- Found a stale worktree from January 15, 2026 that was never cleaned up across dozens of subsequent sessions
Showing cached comments. Read the full discussion on GitHub ↗
8 Comments
Same issue here. Had 15 orphaned agent worktrees from a single session where sub-agents were launched with
isolation: "worktree". All nested under a parent session worktree, none cleaned up on exit. Had to manuallygit worktree remove --forceeach one + delete the branches +git worktree prune.+1 for either automatic cleanup on session exit or a
claude --cleanupcommand.Same issue. Once or twice I _did_ get a prompt when exiting Claude for "Do you want to delete this worktree?" but for some reason it's not showing up the vast majority of the time. Not sure why.
<img width="616" height="864" alt="Image" src="https://github.com/user-attachments/assets/3d1462e8-250c-43f4-a2df-062d5d83b781" />
Reproduction with Agent tool +
isolation: "worktree"Same issue here — using the
Agenttool withisolation: "worktree"to spawn parallel Dev agents during a planning/execution workflow (VBW plugin). A single session created 7 nested worktrees under.claude/worktrees/, none cleaned up on agent completion.What happened
isolation: "worktree"during one session.claude/worktrees/agent-a21cfb32/.claude/worktrees/agent-af7f3668)worktree-agent-*branches, 7 worktree directories with uncommitted changesScreenshot (VS Code Source Control showing all orphan worktrees)
Manual cleanup required
Suggestion
Since sub-agents spawned via the
Agenttool withisolation: "worktree"are ephemeral by design (their commits get merged back to the parent), cleanup should be automatic when the agent returns its result. At minimum, aSessionStartgarbage collection pass would catch leftovers from crashed sessions.Environment: macOS Darwin 25.3.0, Claude Code CLI, Claude Opus 4.6
This comment was drafted by Claude Code on my behalf — the workflow and observations are mine; the phrasing is Claude's.
Adding a data point from a multi-session Claude Code CLI workflow (up to 5 concurrent sessions on one repo, one worktree per session via a fish-function wrapper around
claude).Three pathologies worth categorising separately when thinking about cleanup:
git worktree listnames a path that no longer exists (directoryrm -rf'd withoutgit worktree prune)..claude/worktrees/<name>/exists on disk butgit worktree listdoesn't reference it (typical after a parent-repo relocation, or a create that failed halfway).claude/*branches — aclaude/<name>branch exists with no worktree pointing at it (session exited uncleanly — crash, reboot, kill — before any cleanup could delete the branch).Each needs different remediation, and conflating them into one `
/worktree cleanup` command can be dangerous without guardrails.Our ad-hoc
bin/worktree-gctool runs dry-run-by-default:bin/worktree-gc # dry-run, report only
bin/worktree-gc --apply # safe fixes (prune + -d on merged branches)
bin/worktree-gc --apply-force # also -D unmerged orphan branches
Two design choices I'd suggest for whatever ships upstream:
-d(safe) unless explicitly opted in: silently-D-ing an unmergedclaude/*branch loses uncommitted exploratory work. Gate force-delete behind a flag.Separately from the sweeper, we pair session-exit auto-cleanup (runs in the shell after
claudeterminates — not as a Claude Code hook, so it survives claude crashes but not hard reboots) with the sweeper as a catch-up for the reboot/kill cases. Roughly:Not proposing this as a PR — it's scoped to our repo layout — but flagging the category decomposition in case the cleanup story eventually lands. Happy to share the script as a gist if it's useful.
I'm still having trouble with this same issue.
see my comment here -> https://github.com/anthropics/claude-code/issues/46557#issuecomment-4742119691
it's happening because the sessions with worktrees have no automatic
/nameand my SessionStart hook does that if it;s a worktree.This issue still reproduces. I just published a deep-dive article on Claude Code's worktree isolation that documents this, along with the manual cleanup steps users are relying on in the meantime:
https://my.feishu.cn/docx/O1O0daR6SoSiKsxksiecFjk7nKc
The article also covers the known mitigation — a bash loop to force-remove all
.claude/worktrees/entries — which remains the only reliable cleanup path right now.Still reproducible on v2.1.205 (macOS), specifically for interactive-session worktrees under
.claude/worktrees/(not just subagent ones).The concrete mechanism I hit: Claude locks each session worktree as
locked claude session <name> (pid <N>). On an abnormal exit (SIGKILL / crash / closing the terminal), the lock persists pointing at a now-dead PID, and nothing GCs it —cleanupStaleAgentWorktreesonly targets subagent worktrees, andcleanupPeriodDaysonly governs chat-transcript retention.Because the tree stays
locked, a single--forceis rejected:So recovery requires
git worktree remove --force --force. In repos with an initialized git submodule, there's a second barrier on top (working trees containing submodules cannot be moved or removed), which also needs the double--force.Note this is adjacent to two other reports:
A startup sweep that unlocks/removes worktrees whose lock PID is dead (as proposed in #51643) would close this for session worktrees too.