Stale worktrees are never cleaned up
Status Open
Maintainer reply None cached
Workaround ✓ Mentioned in thread ↓
Activity 14 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
14 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.The category split in @ehagerty's comment is the right safety boundary. I would make the cleanup command prove three separate states before deleting anything:
Then I would keep the action policy conservative:
git worktree pruneis safe for registered-but-missing entries.git worktree remove <path>is review-first if the worktree still exists and has uncommitted changes.git branch -dfirst, with-Donly behind an explicit force flag and after showing the commits that would be lost.rm -rf .claude/worktreescan hide what was active vs abandoned.For a product-level fix, I would prefer two modes: session-exit cleanup for the happy path, plus a dry-run
claude --worktree-gcthat runs on session start and reports stale candidates with age, branch, dirty state, and last known session id before offering deletion.I keep a short AI-agent storage cleanup checklist here, including stale worktrees, session logs, VM bundles, crash dumps, and cache buckets:
https://www.getsafedisks.com/ai-coding-agent-storage-enospc-mac?utm_source=github&utm_medium=issue-comment&utm_campaign=agent-storage-issue&utm_term=stale-worktrees-are-never-cleaned-up&utm_content=anthropics-claude-code-26725
If someone wants a private second pass on a redacted
git worktree list --porcelain+ top-level size table, the $29 cleanup order request is here. No remote access or file contents needed:https://www.getsafedisks.com/checkout?product=pilot_audit&utm_source=github&utm_medium=issue-comment&utm_campaign=agent-storage-issue&utm_term=stale-worktrees-are-never-cleaned-up&utm_content=anthropics-claude-code-26725-private-review
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.One caution on the current "force-remove all
.claude/worktrees/entries" workaround: I would make that a last step, not the first step.Before a loop removes anything, it should print enough evidence to tell whether a worktree contains unmerged or uncommitted user work:
Then the safe tiers are:
git worktree prunefirst, because it only cleans stale metadata for paths already gone.git worktree remove <path>for clean worktrees.git worktree remove --force <path>only after the status/log output shows there is nothing worth preserving.rm -rf .claude/worktreesshould be a final recovery path, because it can bypass git's ability to distinguish registered, nested, dirty, and already-missing worktrees.This matters especially for the nested-worktree cases reported above: deleting the parent directory first can hide the child worktree state and make later branch cleanup more ambiguous.
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.
Adding a data point on scale, and one detail that may widen the issue.
I cleaned up 13 abandoned worktrees holding 13.8 GB today on a single project — roughly 7× the size of the main checkout, accumulated over about 11 days. All 13 were registered in
git worktree listand none had been touched by any cleanup mechanism.Two things that may be useful:
1. Not all of them came from Claude Code. One was under
.claude/worktrees/; the other twelve were created by a different agent CLI operating on the same repo, under/private/tmp. Since every worktree registers into the same.git/worktrees/, a session-start GC in Claude Code would surface and prune all of them regardless of origin. That makes the "scan on session start" half of the request more valuable than the "clean up on exit" half — exit cleanup only ever covers your own.2.
/private/tmpmakes it worse, not better. macOS purges that directory on its own schedule, so those worktrees eventually vanish from disk while their refs persist in.git/worktrees/— producing exactly the "branch cannot be deleted" state described here, with no directory left to point at.A read-only
claude --cleanup --dry-runthat just listed stale worktrees with their branch and whether the directory still exists would have caught this on day one. The removal decision genuinely needs a human — one of my 13 had 69 staged renames that required per-path verification before it was safe to discard — but the detection is trivial and currently doesn't exist anywhere.Data point + two default-behavior proposals from a ~4-month single-repo case study (governance-heavy project, heavy subagent
isolation: worktreeuse, macOS).Scale observed. On 2026-05-26 we cleaned up 113 of 114 worktrees under
.claude/worktrees/, 3.0 GB, plus 112 staleclaude/*branches and 20 stale gitlinks — accumulated over ~2 months at roughly 22–36 worktrees/month with zero session-start visibility. The backlog was found by a human noticing an anomaly, not by any tooling. Post-cleanup, with the newer auto-clean of unchanged agent worktrees, the residual today is 1 worktree / ~99 MB — so the auto-clean clearly helps, but see the second proposal.What our own per-project mitigation converged on, offered here as candidate defaults rather than as a patch:
N worktrees in .claude/worktrees/ (X GB). Run /worktree-sweep to review.The backlog compounds silently precisely because nothing surfaces it at the moment the operator is present. This is the cheapest possible cure for the root cause described in this issue and in #58914, and it is orthogonal to the GC policy itself. (Several comments above already converge on a session-start sweep; the telemetry line is the read-only half that can ship first.).LOAD_BEARING) would have prevented. Heuristic classification should recommend; the operator's explicit mark should win.What we would not propose: auto-deletion by default without the sentinel escape hatch, or heuristic detection of "important" content — that is project-specific and is what the sentinel is for.
Full write-up (classification rule, thresholds, evidence tables) lives in our repo's governance docs; happy to link or paste sections if useful.
Same issue on Windows. I need to manually clean it up, no single worktree can be removed by Claude Desktop.
We need more control of worktrees in the Claude Code app. On a new session either:
And option to auto cleanup on archive or delete of the session.