Windows: stale-worktree cleanup rm -rf traverses NTFS junctions and deletes data OUTSIDE the worktree (~800 GB data loss)
Summary
Claude Code's stale-worktree cleanup deletes worktrees with a recursive rm -rf (Git-for-Windows/MSYS rm.exe). On Windows, that rm does not treat NTFS junctions as links — it descends into them and deletes the target's contents. A worktree containing junctions that point outside itself (in our case: junctions from <worktree>/logs/... into the main repo's gitignored data directories) therefore becomes a landmine: when cleanup removes the worktree, it silently deletes data outside the worktree.
In our case this deleted ~800 GB of collected market data (tick logs, on-chain trade caches, kline caches) whose upstream source retains only ~7 days — most of it permanently unrecoverable. No agent issued any delete command; the deletion is harness housekeeping, so it appears in no session transcript, produced no permission prompt, and bypasses the Recycle Bin.
Environment
- Claude Code 2.1.123, Windows 11 Pro (build 26200)
- git version 2.49.0.windows.1 (its bundled MSYS
rm.exeis what executed the deletions — confirmed via Windows Prefetch) - Worktrees under
<repo>/.claude/worktrees/<name>created by earlier sessions; several hadlogs/(or subdirs of it) junctioned to the main repo (cmd /c mklink /J ...) so worktree sessions could read large gitignored datasets without copying
What happened (timeline, condensed)
- Several worktree sessions went dormant for weeks. During that time, other sessions installed NTFS junctions inside those worktrees (
<worktree>/logs/<subdir>→<main-repo>/logs/<subdir>). - The user reopened the app and revived two of the dormant chats after weeks away. Within ±15 s of the first revival, stale-worktree cleanup ran: four old worktrees present the previous evening were removed (a second batch aligned with the first app activity of the morning).
- The cleanup's
rm -rfwalked through the junctions and deleted the main repo's data behind them. Victim set = exactly the union of the deleted worktrees' junction layouts; siblings that no worktree had junctioned survived.
Evidence
- Windows Prefetch:
RM.EXE(MSYS) executed during the deletion window, preceded byDF.EXE; no interactive shell history (PSReadLine and.bash_historyempty of any such command) — consistent with non-interactive harness execution. - NTFS USN journal: 12k+
File delete | Closerecords for the affected files in the window. - Exhaustive scan of every session/subagent transcript on the machine: no tool call contains the deletion — it was not an agent command.
git worktree list+ directory snapshots: 4 worktrees present on day N evening, gone day N+1 morning; surviving worktrees still carried live junctions into the main repo's data dirs (since manually removed).
Suggested fix
When deleting a worktree (or any harness-owned directory) on Windows:
- Pre-scan for reparse points (
FILE_ATTRIBUTE_REPARSE_POINT) and remove them as links first (rmdiron a junction removes only the link — never the target), then recursively delete the remainder; or - use a deletion routine that refuses to traverse reparse points; and
- ideally log each worktree removal (path + trigger) somewhere durable — this incident took two days to attribute precisely because the cleanup leaves no record.
A defensive extra: if a worktree contains reparse points pointing outside itself, surface a warning / require confirmation instead of cleaning silently.
Repro sketch (safe)
# 1. scratch "precious" data OUTSIDE a repo
mkdir C:\tmp\precious; echo x > C:\tmp\precious\keep.txt
# 2. simulate a stale worktree with a junction into it
mkdir C:\tmp\wt; cmd /c mklink /J C:\tmp\wt\data C:\tmp\precious
# 3. what the cleanup effectively runs (Git-for-Windows rm):
& 'C:\Program Files\Git\usr\bin\rm.exe' -rf C:\tmp\wt
# 4. C:\tmp\precious\keep.txt is gone on affected rm builds
Happy to provide any further forensic detail (timestamps, USN excerpts) privately if useful.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗