[BUG] In 2.1.141, stale-worktree cleanup uses `rm -rf` as a fallback and silently deletes untracked code
Resolved 💬 2 comments Opened May 14, 2026 by officiallygeorge Closed May 16, 2026
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Working on an epic with multiple sub-issues.
- 16:28:15 – I approve agent to merge PR 273 with
gh pr merge 273 --squash --delete-branchbefore going back to resume work on issue 268 which we had started in the same session before realizing that it was blocked on 273. - 16:34:26 – The agent spins up a background process running a post-merge benchmark script that takes 10-15 minutes to complete and writes logs.
- 16:37:34 – Claude Code's stale-worktree cleanup detects the squash-merged branch and attempts to run
git worktree remove --forceon.claude/worktrees/sweet-davinci-fce058 - 16:37:34 – The command fails with
error: failed to delete '...sweet-davinci-fce058': Directory not emptybecause benchmarks are still writing to the directory and the untracked changes for issue 268 are still present. - 16:37:34 –
~Library/Logs/Claude/main.logshows:[warn] Git worktree remove failed, will try manual cleanup. Manual cleanup is, completely insanely,rm -rf. - 16:45:52 – Agent wakes and realizes that worktree has been deleted:
[info] [CCD] Worktree at ...sweet-davinci-fce058 was deleted; falling back to origin repo.
It looks like the cleanup runs ten minutes or so after any squash-merge irrespective of what's running in the worktree, and if it cannot remove the worktree via git worktree remove it uses rm -rf without warning the user.
What Should Happen?
Let me put this part in big letters:
AT NO POINT SHOULD CLAUDE CODE RUN rm -rf ON A WORKTREE WITH UNTRACKED CHANGES IN IT.
- Do not rely on the 'archive' status of a session in Claude Desktop – the new auto-archiving sensitivity is broken and infuriating. I spend significant time hitting the charmlessly-labeled "Unarchive" button in response to the app archiving sessions that it thinks are idle (but are not).
- When
git worktree remove --forcefails, instead of falling back to SILENTLY IRREVOCABLY DELETING EVERYTHING, use a cheap model to investigate and either move the untracked files + worktree to a temporary.claude/orphaned/-type path, or propose a plan to the user.
Error Messages/Logs
Steps to Reproduce
- In a Claude Code session, start work on a feature branch inside an auto-created agent worktree at
<repo>/.claude/worktrees/<name>. - Open and squash-merge a PR for that worktree's branch (e.g.
gh pr merge <N> --squash --delete-branch). - Before ~9 minutes elapse, do one of (each independently reproduces):
- Leave modified-but-uncommitted files in the worktree, OR
- Start a long-running process (e.g. docker compose run ... with a volume mount writing into the worktree's output/ directory).
- Wait until Claude Code's stale-worktree cleanup polls the PR state (observed: ~9 min after the merge).
- Cleanup runs
git -c core.longpaths=true worktree remove --force <worktree>and getserror: failed to delete '<worktree>': Directory not empty. Then a[warn] Git worktree remove failed, will try manual cleanupmessage is logged. - A few seconds later, the worktree directory is gone. Any in-flight process that was writing into it crashes with FileNotFoundError / ENOENT. Any uncommitted edits the user made are lost.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.140
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗