[BUG] Claude constructs &&-chained command with ||-tolerant loop + trailing rm -rf, swallowing failures and wiping working tree
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: Data loss (recoverable in this case via git index, but the recovery wasn't obvious and the wipe shouldn't have happened)
What happened:
During a batch directory restructure, Claude (Opus 4.7 via Claude Code) generated a Bash command of roughly this shape:
mkdir -p NEW_TREE \
&& for sub in dir1 dir2 dir3; do
git mv "OLD_TREE/$sub" "NEW_TREE/$sub" || echo "Failed: $sub"
done \
&& rm -rf OLD_TREE
Every git mv in the loop failed (Windows case-insensitive filesystem treated OLD_TREE and NEW_TREE as the same path → fatal: Invalid argument). The || echo swallowed each failure into a successful loop exit. The &&-chained rm
-rf then ran and wiped the working-tree contents of two directories totalling ~60 tracked files.
Recovery succeeded only because git's index still held the staged renames, and git restore --worktree -- <paths> rehydrated the files.
Why this is a Claude reasoning bug, not a tool bug:
The platform's Bash tool did exactly what was asked. The agent should not have:
- Combined a ||-protected loop (which guarantees loop-success regardless of inner failures) with a trailing && to a destructive command. Those two patterns compose into "destruction always runs."
- Used rm -rf for cleanup at all when git mv-only would have achieved the same restructure.
The system prompt's "Executing actions with care" section warns about hard-to-reverse operations and rm -rf specifically, but the agent's chain construction circumvented that guidance.
Suggested mitigations on the model side:
- Pattern-detect ||-suppressed loop + trailing &&-chained destructive op as a high-risk shape and refuse / flag.
- Require set -e (or equivalent strict-mode reasoning) when authoring multi-step Bash chains where any step is destructive.
- Default to git mv exclusively for restructuring tracked files; treat rm -rf on tracked paths as needing explicit user confirmation per turn.
Repro context: Windows 11, Git on bash (Git for Windows MINGW64), case-insensitive NTFS, repo with mixed case-only directory rename intent.
What Should Happen?
see explanation above
Error Messages/Logs
Steps to Reproduce
Environment
- OS: Windows 11 Pro 10.0.26200, NTFS (case-insensitive)
- Shell: Git Bash (Git for Windows MINGW64) within Claude Code
- Model: Claude Opus 4.7 (model ID claude-opus-4-7)
- Claude Code version: (grab via claude --version if you want — I don't have it cached)
- Repo state at incident: main branch, ~11 commits ahead of origin, mid-session restructure with multiple staged renames
Trigger sequence (paraphrased from the actual session)
The session was a long sequence of Setup/ folder restructures. Trigger conditions:
- Earlier in session, user asked: "rename folders under Setup/staging to be uncapitalised." This was completed successfully via two-step renames through _tmp intermediate names. This established a working pattern in Claude's
context.
- Later, after several other moves, user asked: "rename source to Source" and "rename staging to Staging" in quick succession (case-only changes in the opposite direction).
- Claude tried git mv source Source directly → fatal: renaming 'Setup/source' failed: Permission denied (Windows directory lock from a recently-run subprocess).
- Claude pivoted to children-individually approach AND in the same chain pre-created Source/ and Staging/ parent dirs — but those mkdirs on case-insensitive Windows resolved to the existing source/ and staging/ (no-ops).
- Claude then ran a single chained command roughly:
mkdir -p ../Setup/Source ../Setup/Staging \
&& for sub in artwork docs inno install_shield; do
git mv "../Setup/source/$sub" "../Setup/Source/$sub" || echo "Failed: source/$sub"
done \
&& for sub in artwork docs help maps plugin tutorials; do
git mv "../Setup/staging/$sub" "../Setup/Staging/$sub" || echo "Failed: staging/$sub"
done \
&& rm -rf "../Setup/source" "../Setup/staging" \
&& ls "../Setup/"
- All git mv calls failed with fatal: ... failed: Invalid argument (case-insensitive FS treated source/X and Source/X as the same path).
- Loops returned exit 0 because of || echo "Failed: $sub".
- && chained through to rm -rf which wiped both directory subtrees.
Expected behaviour
After all git mv calls fail, the agent should NOT proceed to rm -rf originals. Failure of the move = the originals still hold the data and must not be deleted.
Actual behaviour
~60 tracked files removed from working tree. Recovery via git restore --worktree -- <paths> succeeded because git's index still held the file blobs at the original paths.
Minimal mechanical repro (without involving an agent)
# Demonstrates the shell pattern itself, not the agent failure:
git init demo && cd demo
mkdir foo && echo "test" > foo/a.txt
git add . && git commit -m initial
mkdir Foo \
&& for f in a.txt; do git mv "foo/$f" "Foo/$f" || echo "Failed: $f"; done \
&& rm -rf foo
# Result on Windows: foo/a.txt is gone from working tree.
# git restore --worktree -- foo can recover it.
What the agent should have generated instead
Two-step rename via non-colliding intermediate, which the agent had successfully used earlier in the session for the inverse rename:
git mv ../Setup/source ../Setup/source_xfer
git mv ../Setup/source_xfer ../Setup/Source
No rm -rf, no ||-tolerant loops, no destructive fallback.
Suggested questions for triage
- Should the model refuse to chain && after a ||-tolerant loop when the trailing op is destructive?
- Should rm -rf against tracked paths require explicit per-turn user confirmation, regardless of operating mode (auto / non-auto)?
- The agent had successfully used _tmp-intermediate two-step renames earlier in the session. Why was that pattern not retrieved when reverse case-only renames came up?
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.123
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗