Worktree cleanup deletes live worktrees when a repo is opened from both Windows and WSL (gitdir path-spelling mismatch makes every cross-side worktree look prunable)
Summary
When one repository is reachable from both Windows (C:\dev\Projects\repo) and WSL (/mnt/c/dev/Projects/repo) — one working tree, one shared .git — each side's git reports the other side's live worktrees as prunable. Claude Code's automatic worktree cleanup then deletes those registrations, killing worktrees that are actively in use by a session on the other side.
This is a different root cause from #76144 feeding the same sink. There, gitdir is corrupted to the literal string .git; here gitdir is perfectly valid — just written in the other OS's path spelling.
Mechanism
.git/worktrees/<name>/gitdir stores an absolute path in the creating side's spelling, and neither side can stat the other's:
| registration created by | gitdir contains | exists on disk | Windows git | WSL git |
|---|---|---|---|---|
| Windows session | C:/dev/Projects/repo/... | yes | ok | prunable |
| WSL session | /mnt/c/dev/Projects/repo/... | yes | prunable | ok |
On Linux, C:/dev/... is a relative path (a component literally named C:); on Windows, /mnt/c/dev/... resolves to C:\mnt\c\dev\.... Neither exists, so each side concludes the other's worktrees are stale.
So prunable here does not mean stale — it means created by the other side. Anything that prunes on that signal destroys live state.
Reproduction
- From Windows, in a repo under
C:\dev\Projects\repo, start a Claude Code session so it creates a worktree under.claude/worktrees/. - From WSL:
cd /mnt/c/dev/Projects/repo && git worktree list --porcelain→ the Windows worktree is flaggedprunable gitdir file points to non-existent location, while its directory plainly exists. - From WSL:
cd /mnt/c/dev/Projects/repo && claude— start a session, issue no git commands, exit. - Back on Windows,
.git/worktrees/<name>for the still-open session is gone.
Observed
A live session worktree vanished between two consecutive git calls, mid-task. Afterwards, in the still-running session:
- every git command in that worktree fails with
fatal: not a git repository: .../.git/worktrees/<name> - a
git show > fileredirect truncated a working file to zero bytes when git failed git worktree repaircannot undo it — it repairs moved worktrees, not deleted registrations- the only recovery is hand-reconstructing the admin directory, or copying the changes into a fresh worktree to commit
In a repo with 40 worktree directories, only 18 registrations survived; the rest were casualties of prunes from one side or the other.
Suggested fix
An unstattable gitdir is not evidence of staleness. Cleanup should skip a registration whose gitdir path it cannot resolve, rather than treating unresolvable as dead — the conservative direction, since the cost of skipping a genuinely stale entry is a leftover directory, while the cost of a false positive is destroyed work in a running session.
Narrower options, if a general rule is unwelcome: recognise the C:/… ↔ /mnt/c/… correspondence before judging, or exempt registrations whose worktree directory exists under either spelling.
More broadly, and shared with #76144 and the other cleanup reports: prunable alone should not authorise automatic deletion. Git offers that flag to a human running an explicit command, who can see the repo state; automatic cleanup acting on the same flag has no such context.
Environment
- Windows 11 Pro, WSL2 (Ubuntu), repo on NTFS accessed from WSL via
/mnt/cwithmetadataautomount - Both a native-Windows and a WSL Claude Code installation, same repository
- Trigger is a WSL-side session start with no explicit git commands, so it cannot be caught by allowlists or hooks
Related
- #76144 — same symptom (healthy worktree flagged
prunable, then reclaimed), different cause (gitdirwritten as literal.git). A fix for that truncation would not address this. - #50909 — same Windows/WSL shared-repo setup destroying work, via a different mechanism (
git reset --hardcollision between concurrent sessions).