Windows: stale-worktree cleanup rm -rf traverses NTFS junctions and deletes data OUTSIDE the worktree (~800 GB data loss)

Open 💬 2 comments Opened Jul 7, 2026 by bedroomlab

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.exe is what executed the deletions — confirmed via Windows Prefetch)
  • Worktrees under <repo>/.claude/worktrees/<name> created by earlier sessions; several had logs/ (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)

  1. 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>).
  2. 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).
  3. The cleanup's rm -rf walked 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 by DF.EXE; no interactive shell history (PSReadLine and .bash_history empty of any such command) — consistent with non-interactive harness execution.
  • NTFS USN journal: 12k+ File delete | Close records 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:

  1. Pre-scan for reparse points (FILE_ATTRIBUTE_REPARSE_POINT) and remove them as links first (rmdir on a junction removes only the link — never the target), then recursively delete the remainder; or
  2. use a deletion routine that refuses to traverse reparse points; and
  3. 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗