Worktree sessions reuse an existing worktree directory from a previous session instead of creating a fresh one
Environment
- Claude Code 2.1.197 (desktop app session with worktree isolation)
- macOS (Darwin 24.6.0)
What happened
Starting a new session (new task, worktree isolation enabled) placed the session inside an existing worktree directory that a previous session had created for an unrelated task, instead of creating a fresh worktree. A new branch was created for the new session, but it was checked out into the old worktree directory.
The reflog of the reused worktree shows the history clearly (paths/branches lightly sanitised):
072bd99 HEAD@{2026-07-20 09:14}: checkout: moving from <detached> to claude/<new-task-branch> <- new session reuses dir
cff5b74 HEAD@{2026-07-18 14:19}: checkout: moving from claude/<old-task-branch> to HEAD
cff5b74 HEAD@{2026-07-17 10:02}: checkout: moving from <detached> to claude/<old-task-branch>
cff5b74 HEAD@{2026-07-15 13:58}: commit: (previous task's commits)
So .claude/worktrees/<old-task-name>-<hash>/ (directory named after the previous task) is now hosting a branch for a completely different task.
Expected
Each new session/task with worktree isolation should get a fresh worktree directory named after its own task/branch, created off the default branch.
Impact
- The worktree directory name no longer matches the branch/task, which is confusing when several worktrees exist.
- Leftover untracked/ignored files from the previous task can silently leak into the new task's context or PR.
- This has happened repeatedly across sessions in the same repo (the user has had to add a standing instruction telling the model never to reuse existing worktrees, but the reuse happens at session setup, before the model can influence it).
Possibly related observation
In the same session, background subagents (Agent tool) appeared to default their working directory to the main checkout rather than the session's worktree — two subagents wrote files to <repo>/docs/... instead of <repo>/.claude/worktrees/<worktree>/docs/... and the files had to be moved manually. If subagent cwd inheritance is a separate issue I can file it separately.
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
The worktree collision is happening because the session initialization is checking for an existing worktree directory by name but not verifying whether it's stale from a previous session. Git's
worktree listwill show the directory as "valid" even if the original session that created it is long gone.Two failure modes here:
Root cause check:
Look at how the worktree directory name is generated. If it's deterministic (e.g., based on repo name only), collisions are inevitable. If it includes a session ID, the cleanup logic isn't working.
Fix suggestions:
git worktree listfor the parent repo. If not, it's an orphan and should be deleted.``
bash
``git worktree prune
find /tmp/worktrees -type d -mmin +60 -name "claude-session-*" -exec git worktree remove --force {} \;
This is tracked in the memory + session management topic — worktree cleanup is part of the broader ephemeral state problem.
---
Context: tracking memory + session management across agent runtimes. SwarmAI. Discussion: T-MEM
Confirming on macOS (26.5.2, Claude Code 2.1.212, Desktop app) — hit this repeatedly this week, including once where the adopted folder belonged to a still-live session. We ran controlled tests today and can add the trigger, the mechanism behind your reflog, and a detection heuristic.
The trigger is archiving: an archived session's folder returns to a reuse pool, deterministically.
.claude/worktrees/X-79ed71on branchclaude/X-79ed71(names match — this is what fresh looks like).claude/Y-d6f752but checks it out into the old folderX-79ed71. Folder birth time predates the session; folder name no longer matches the branch. The old branch survives unattached with its commit; any uncommitted/ignored files are inherited by the new session.Archive detaches HEAD but never removes the folder — detached folders are the pool. Watching
git worktree listwhile archiving a session: the folder flips from[claude/<branch>]to(detached HEAD)and stays on disk. That's your reflog linemoving from claude/<old-task-branch> to HEAD; the latermoving from <detached> to claude/<new-task-branch>is the reuse. Notably, archiving a session whose branch had been renamed offclaude/*did not detach it — ownership appears keyed to the mintedclaude/*branch name. Consistent with that, the session registry can disagree with reality: one recycled session's registry entry showed its minted branchclaude/hello-<hash>while its actual cwd was a pre-existing folder from a different task.Live sessions are affected too: a live session's folder whose branch had been renamed to a team convention (
feat/<ticket>-…) was adopted by a brand-new session while the first was still open — its checkout switched out from under it.Detection heuristic / workaround (we run this as a session-start gate now): a fresh worktree has folder basename == branch minus
claude/, a just-now birth time, and clean status. Any of these failing means a recycled folder:Expected: a worktree-enabled new session always creates a new folder, and archive either removes the worktree (
git worktree remove+ prune) or permanently retires it — never feeds it to the next session, uncommitted files and all.The worktree-directory reuse bug you are hitting is a symptom of the session harness using a content-addressed or fixed-pattern directory name (typically derived from the worktree branch prefix) rather than a session-unique identifier. When the old branch was cleaned up but the worktree directory was not pruned, the next session sees the directory as available and checks into it.
Two observations from running long-lived parallel agent fleets that might be useful context for the Anthropic team:
In the meantime:
git worktree prunebefore starting a new session clears the stale entries, though it does not prevent the incorrect allocation if the directory still exists on disk.Confirming this independently, with a slightly different angle: direct evidence from the app's own local state, not just git reflog.
Desktop keeps a worktree lease registry at
~/Library/Application Support/Claude/git-worktrees.json. On my machine it currently shows entries like:The directory name (
inventory-sync-cpu-spike-1e5b7c) is from a completely unrelated earlier task; thebranchfield shows it's currently leased to a different session on a different branch entirely. TheleasedByfield name itself confirms @xni06's theory in the comment above — this is a deliberate lease pool, not an accidental collision. Archiving a session appears to release the lease (making the slot available for reuse) rather than actually removing the git worktree, which is presumably why cleanup settings (ccAutoArchiveOnPrClose, confirmed enabled in myclaude_desktop_config.json) don't prevent this from recurring.Worth noting there's no documented setting to opt out of this pooling — checked the settings reference and worktrees docs and found nothing under a
worktree.*namespace for it. For anyone relying on the worktree folder name to identify which task is which (e.g. picking the right one to open in an IDE), this makes that unreliable once a slot has been recycled even once.I am experiencing this issue as well on Claude for Windows 1.24012.9 (03c61d)
The lease registry finding from @kieranbenton (~/Library/Application Support/Claude/git-worktrees.json) is the missing piece that explains why git worktree prune does not help here: the app is managing a pool at the application layer, separate from git's own worktree tracking. Git sees valid worktrees (or prunable ones), but the app's pool decision happens before git is consulted.
This matters for the fix design. The corruption is not a git bookkeeping error - it is an application-level pool allocator that releases a slot (on archive) but does not clean the directory, then hands that dirty slot to the next session. The fix belongs in the pool allocator: release should include a git worktree remove, not just a lease entry update.
A few observations from running long-lived parallel agent fleets that may be useful context for the Anthropic team:
The leasedBy field in the registry suggests the app already has the concept of "a session owns this worktree" - the gap is that archive clears the owner without removing the directory, creating the pool-of-dirty-slots problem. If archive ran git worktree remove --force on the slot before clearing leasedBy, the pool would only contain genuinely clean entries.
The case @xni06 documented - a live session's folder being adopted while it was still open - is the harder variant. That happens when the branch inside the worktree was renamed off the claude/* prefix, which apparently releases the lease even though the session is active. The fix there is to key lease ownership on the session ID (from leasedBy), not on the branch name pattern.
For anyone running multiple parallel agent sessions right now and hitting this: the detection script @xni06 posted works reliably. We use a similar check as a pre-task gate: compare folder basename to branch suffix, verify birth time is within the session window, and run git status --porcelain to catch inherited untracked files before the agent sees them.
@kcarriedo nailed it — the pool decision happens at the application layer, above git, and that fact generalizes further than it might seem. Any mitigation working below the app (pruning, cleaning, renaming branches) can just get overridden by the allocator on its next pass. That's why
git worktree prunenever actually helps here. So we gave up on detecting/cleaning pooled slots and just stopped using them entirely.Our workaround: create the worktree yourself, outside the repo, with the harness checkbox unticked.
Start the session with the worktree box unticked, so the app never allocates a slot for you. Then, from the main checkout:
...and anchor the session to that path using the native
EnterWorktreetool.Why this actually works: the pool only tracks folders the app itself created and logged in
git-worktrees.json. If you create the folder yourself, it never shows up there — no lease to release, nothing for the allocator to reuse. Easy to check on your own machine: comparegit worktree listagainst the registry's keys. Self-created worktrees never appear in the registry, every.claude/worktrees/folder does, and anything sitting atleasedBy: nullis basically waiting in line to be reused.We'd tried a detection gate before landing on this approach. It works, but only catches the problem after you've already been handed a dirty slot — which is strictly worse than avoiding the handoff in the first place.
A few nice side effects fall out of just owning the path yourself: the folder name stays as your ticket key permanently (which also solves @kieranbenton's issue about folder names becoming meaningless over time), the branch gets its real name right from creation, and being outside the repo altogether sidesteps the nested-
.claude/discovery issues tracked separately in #48967 and #69026.To be upfront about the trade-offs:
git worktree remove+git branch -d. Not much of a downside though, since archive doesn't reclaim harness-created worktrees either.EnterWorktree:/clearresets the session's cwd back to its launch directory and silently drops the anchor. After a clear, you're back at the main checkout while the worktree still sits on disk — so anything that writes files needs to re-anchor first, or your edits land on the default branch instead. Easy to work around once you know about it, easy to get burned by if you don't.To be clear, none of this actually fixes anything. The underlying allocator behavior still needs to be addressed, and the real bug is that archive releases a lease without removing the directory. This is just a way to sidestep the problem today, without needing a setting that doesn't currently exist.