[BUG] Claude constructs &&-chained command with ||-tolerant loop + trailing rm -rf, swallowing failures and wiping working tree

Resolved 💬 3 comments Opened Apr 30, 2026 by dgould1 Closed May 31, 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?

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:

  1. Pattern-detect ||-suppressed loop + trailing &&-chained destructive op as a high-risk shape and refuse / flag.
  2. Require set -e (or equivalent strict-mode reasoning) when authoring multi-step Bash chains where any step is destructive.
  3. 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:

  1. 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.

  1. 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).
  2. Claude tried git mv source Source directly → fatal: renaming 'Setup/source' failed: Permission denied (Windows directory lock from a recently-run subprocess).
  3. 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).
  4. 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/"

  1. All git mv calls failed with fatal: ... failed: Invalid argument (case-insensitive FS treated source/X and Source/X as the same path).
  2. Loops returned exit 0 because of || echo "Failed: $sub".
  3. && 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_

View original on GitHub ↗

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