Windows: agent-created node_modules junctions from git worktrees cause source-file deletion on worktree remove
Summary
During a ticket-chain run on Windows, the agent created Windows junctions to share node_modules between feature git worktrees and the main repo. When git worktree remove --force cleaned up the worktrees afterward, it followed the junctions recursively and deleted source files from the main repo's working tree.
All data was recoverable via git restore . (the commits had already been pushed), but the failure mode is silent and surprising.
What happened, step by step
- Main repo:
C:\repo\securish-bike-parking- an npm-workspaces monorepo withapps/api,apps/web,packages/contracts - Agent created 4 feature worktrees at sibling paths (
../wt-bkfk2-57, etc.) from main - Agent created Windows junctions so the worktrees could share the main repo's
node_modules(to avoid re-runningnpm installfour times):
````
cmd //c mklink //J "C:\repo\wt-bkfk2-57\node_modules" "C:\repo\securish-bike-parking\node_modules"
- Agent implemented tickets in the worktrees, committed, merged each branch to main, pushed
- Agent removed each worktree:
````
git worktree remove --force /c/repo/wt-bkfk2-57
- On Windows, git's worktree removal uses recursive directory deletion that follows junctions (analogous to
rmdir /S /Q). The chain was:
- Delete
wt-bkfk2-57\node_modules→ this is a junction → follows intosecurish-bike-parking\node_modules - Inside
securish-bike-parking\node_modules\@bkfk2\api→ npm workspace junction → follows into../../apps/api - Result:
securish-bike-parking\apps\api\contents deleted
Why the npm workspace chain exists
npm workspaces on Windows creates junctions (not POSIX symlinks) for workspace packages. So node_modules/@bkfk2/api is a Windows junction pointing to ../../apps/api. Any recursive delete that follows junctions will traverse from node_modules → workspace source directories.
Impact
Source files under apps/api/, most of apps/web/, and packages/contracts/ were wiped from the working tree. Files that the merges had just written back (App.tsx, identity.ts, etc.) were the only survivors. git restore . recovered everything cleanly since the code was in the index.
Guidance requested
The agent chose this junction approach to avoid the cost of four parallel npm install runs. That tradeoff is reasonable in intent, but the Windows junction-following behavior made it catastrophic.
Two things would prevent this:
- Agent-side: never create a Windows junction from a feature worktree that points into the main repo's
node_modules. Safer alternatives:npm installper worktree (npm cache makes it fast), or copy / reference node_modules with a plain path env var rather than a filesystem junction.
- Tooling-side: if Claude Code's worktree-removal path on Windows can detect that a directory being removed contains a junction pointing outside the worktree's own tree, it could warn or refuse rather than silently following the junction into the parent repo.
Environment
- Windows 11 Pro
- Git for Windows / MSYS2 Bash
- npm workspaces monorepo
- Claude Code agent running the
/tch(ticket-chain) skill
4 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Additional repro on Windows + pnpm workspaces (axon, 2026-05-21)
Hit this exact bug today on a pnpm-workspaces monorepo. Posting as
additional repro data — same junction-following mechanism, slightly
different surface.
Environment
apps/dashboard/(Next.js 16) +packages/{config,env,prisma}/<repo>/.claude/worktrees/<adj>-<name>-<hash>/(the Cowork-tab worktree naming pattern)
What happened
its own worktree under
.claude/worktrees/git worktree listentries are gone), but their session jsonl folders still exist at
~/.claude/projects/F--source-repos-<repo>--claude-worktrees-*/disappeared after restart (still searchable in settings but no longer
navigable)
the root
node_modules/of the active main repo was emptiedrunning state at 16:37:16 — 26 seconds before the wipe
Surface vs OP
OP saw
apps/api/source files wiped vianode_modules/@scope/api→ workspace junction follow.In my case only the root
node_modules/contents were wiped —source files in
apps/dashboard/,packages/env/,packages/prisma/were untouched. The workspace
node_modules/subfolders had alreadybeen mostly empty since pnpm install activity in late April (pnpm's
virtual store keeps real packages in
.pnpm/, so workspacenode_modules/only hold a.modules.yamland the workspace-internalsymlinks). So the deletion chain seems to have stopped at the root
node_modules/level.Trace gap
There is no Bash tool call in any Claude session jsonl (across
~160 sessions for this project) that matches
rm -rf node_modules,rimraf,Remove-Item .* node_modules, orgit worktree remove --forcein the 16:25-16:50 window. The destructive operation was triggered
inside Claude Desktop's restart cleanup path, not via the agent's
visible tool calls — confirming OP's tooling-side observation that
worktree removal on Windows needs junction-aware deletion logic.
Impact + recovery
<repo>/node_modules/root (~45 MB of symlinks +.pnpm/virtual store)pnpm install --frozen-lockfile-> 44.5 s (pnpm-store cachewas intact, so 749 reused / 763 added)
Suggested addition to OP's mitigation #2
If Claude Desktop's restart cleanup runs
git worktree prune/git worktree remove --forceon Windows, the same junction-aware checkshould apply there, not only on agent-initiated worktree removal. The
restart-time cleanup hit me without any agent action.
Happy to provide additional repro data (session IDs, exact jsonl
timestamps, folder mtime evidence) if it helps narrow down the
restart-cleanup code path.
Addressed by a merged fix. Please reopen with a fresh repro if you still see this on a current version.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.