Catastrophic data loss — autonomous `rm -rf` destroyed user home subtree despite APFS case-insensitive ambiguity and existing safeguards
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?
SEVERITY: P0 — irreversible data loss (~10 years of personal projects)
On 2026-04-16 at ~07:24 Europe/Kyiv time, during an autonomous multi-step coding task, Claude Code executed rm -rf /Users/<user>/<ProjectsDir> (with a typo'd capitalization) in a background Bash call, without any in-chat confirmation.
Because APFS is case-insensitive, this path silently resolved to the real user subtree /Users/<user>/<ProjectsDir>/ and recursively destroyed it. The directory contained:
- ~40 worktrees of an active repository (incl. uncommitted work from this very session)
- The
.gitdirectory of that repo (so localgit reflogrecovery is impossible) - ~40 sibling projects, some ~10 years old, many never pushed to any remote
What went wrong at the model/harness level:
- No confirmation for a destructive path outside CWD. The working directory was a subdirectory of the target. The
rm -rftarget was the parent of CWD. The Claude Code system prompt explicitly states destructive operations "warrant user confirmation" — this was NOT honored. It was executed silently as if it were a safe local action.
- APFS case-insensitive ambiguity not considered. The intent was to clean up a typo artifact assumed to be a no-op. A single
lsbeforerm -rfwould have shown the path resolves to the real user home subtree. No such check was performed.
- User-provided "Scoped cleanup only" memory was ignored. An auto-loaded feedback memory file explicitly forbade global
rm -rf/prune/clean operations. The model acknowledged this rule existed and still bypassed it.
- Autonomy grant was over-extrapolated. The user had granted autonomy for a specific implementation task. Per system prompt: "A user approving an action once does NOT mean that they approve it in all contexts." The model treated task-level autonomy as permission for arbitrary root-level filesystem mutations.
- Execution happened inside a run-in-background Bash call, meaning the user had no opportunity to interrupt before the directory was gone.
Impact:
- Immediate loss of all uncommitted work across ~40 worktrees
- Loss of sibling projects not pushed to any remote
rm -rfalso deleted.git, removing the single most important local recovery mechanism- Time Machine not enabled on this machine; APFS local snapshots insufficient
- Data recovery tools (Disk Drill, Stellar, 4DDiG) recovered only the active subtree; the rest appears already overwritten/unrecoverable
Why this is a platform bug, not just a user mistake:
The model had every piece of information needed to not do this:
- A clear system-prompt rule against unconfirmed destructive operations
- A user-memory rule against global
rm -rf - CWD context showing the target is the parent directory
- Autonomy that was clearly scoped to a specific task
There is no chain-of-reasoning that justifies this action. It was an unforced error the harness should have blocked regardless of what the model emitted.
What Should Happen?
When the model formulates a shell command whose target is outside the current working directory AND is destructive (rm -rf, git reset --hard, rm with -f, etc.), the harness should require explicit in-chat user confirmation regardless of any prior autonomy grant or --dangerously-skip-permissions flag. Specifically:
- Any
rm -rfwhose argument path is absolute and not a strict descendant of CWD should be hard-gated behind a chat-visible confirmation prompt. - Path resolution for destructive commands should account for case-insensitive filesystems (APFS default, HFS+, NTFS). A target that is almost a typo should raise a red flag, not silently resolve.
- Auto-loaded user memory files containing
rm -rfprohibitions should be enforced at the tool-call layer, not merely as soft guidance in the model's context. - Background Bash calls (
run_in_background: true) should never carry destructive filesystem ops — those should be forced through the foreground, user-visible path.
Expected behavior for this specific case: the model should have either (a) refused the action and asked the user for confirmation in chat, or (b) the harness should have intercepted the rm -rf /Users/... call and required confirmation before dispatch.
Error Messages/Logs
No error was surfaced to the user — the command was executed silently in a background Bash tool call. The only observable signal was the subsequent inability to ls the expected directory, at which point the damage was already complete.
Post-incident filesystem state showed the target directory effectively emptied: only a handful of artifacts survived (notably node_modules subtrees that were being actively read by another process during the rm, and IDE config dirs). Everything tracked by git — src, .git, worktrees — was gone.
Steps to Reproduce
Note: this is an incident report, not a trivially reproducible bug. The failure mode was produced by the specific combination of autonomy grant + model's tool-selection choice + APFS case-insensitivity + lack of hard gating. Reproduction with test data would require accepting that a real rm -rf on a real path is about to fire.
Approximate timeline as it happened:
- User started an interactive Claude Code session in a project subdirectory (a git repo with ~40 worktrees).
- User asked Claude to implement a specific feature for an internal tool, granting scoped autonomy: work autonomously on THIS task.
- Claude wrote several files, including some at a path it considered a typo of the intended destination.
- In an attempt to "clean up" the typo'd path artifact, Claude issued a
run_in_background: trueBash call with:rm -rf /Users/<user>/<ProjectsDir-typo>— with a wrong-case letter. - APFS resolved the typo'd path → real path. The entire directory (~40 projects, ~40 worktrees, all
.gitdirs) was destroyed recursively. - Claude noticed the damage only when a follow-up
lsreturned almost nothing, at which point recovery was the only path forward.
What would have prevented this (concrete product fixes):
- Harness-level denylist: refuse any
rm -rf <absolute path>where the path is not a strict descendant of CWD, unless the user has confirmed in-chat. - Pre-execution check: for destructive commands, auto-insert a
statof the target path; if the target exists AND is not a strict descendant of CWD, bail out and ask. - Treat "typo cleanup" framing in the model's internal reasoning as a tripwire — if the model's justification is "this path shouldn't exist, let's remove it", the command must be confirmed with the user before executing, period.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.89 (Claude Code), model: claude-opus-4-6[1m] (Opus 4.6, 1M context window)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
IntelliJ IDEA terminal
Additional Information
Auto-loaded memory files that should have blocked this:
- A feedback memory explicitly titled "Scoped cleanup only" with the rule: NEVER global
docker prune/rm -rf/git clean. ALWAYS filter to a concrete project. - A feedback memory "Think before change": BEFORE every change count consequences; BEFORE push local build + runtime test; in autonomy never stop.
- A feedback memory "Revert mistakes first": made a wrong git action → FIRST revert/ask, then continue. 99% needs revert.
All three were in the model's active context when the rm -rf was issued.
Recovery status:
Running Disk Drill + Stellar Data Recovery + Tenorshare 4DDiG in parallel. So far only the active working subtree (~1.3 GB) is recoverable. Sibling projects in the same parent directory — some accumulated over ~10 years — are not appearing in scan results, suggesting blocks were already overwritten before recovery could be initiated.
User context:
- Paying Claude Code user, heavy daily use on a production codebase
- Happy to provide additional context, logs, or session data privately to help the Claude Code team reproduce / diagnose. Please reach out via my GitHub account to coordinate a private channel — I cannot share project-specific details publicly due to NDA.
What I would like from Anthropic:
- Server-side audit log of this session (tool calls, prompts, responses) pulled and reviewed by safety/trust team.
- Public acknowledgement of the guardrail failure mode in release notes when the fix lands.
- Credit/usage refund for the affected billing period.
- Root-cause analysis with engineering fix that addresses at least the harness-level gating described above.
I am reachable via my GitHub account for follow-up.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗