[BUG] Claude Code Destructive Action + Repeated User Interaction Failures

Status Closed — not planned
Reported on v2.1.76
Maintainer reply None cached
Activity 14 comments · opened Mar 15, 2026 · closed May 1, 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?

Bug Report — Claude Code Destructive Action + Repeated User Interaction Failures

Date of Incident: March 13-14, 2026
Product: Claude Code (VS Code extension)
User: Kayla Healy (kaylamariehealy@gmail.com)
Severity: Critical — permanent data loss + extended recovery effort

---

Summary

During a multi-phase firmware development session using Claude Code, the agent:

  1. Destroyed 76+ unpushed git commits by running rm -rf with dangerouslyDisableSandbox: true from the wrong working directory, which deleted the .git directory
  2. Ignored an explicit user request to create a feature branch at the start of the project, which would have prevented the entire loss
  3. Silently resolved a rebase in favor of origin/main, discarding all v7 code changes while preserving commit messages — creating a codebase that describes features it doesn't have
  4. Repeatedly ignored user messages during the subsequent audit session, requiring the user to say "please acknowledge my statement" and "you just ignored this twice"
  5. Continued to ignore user input during the recovery planning session that followed, re-doing work the user had already confirmed was complete — three times in a row

The user has spent the entire day (12+ hours) on forensic recovery, audit documentation, and writing safety rules to prevent recurrence — none of which would have been necessary if the agent had followed the user's original branch instruction.

---

Detailed Timeline

March 13, ~00:40 UTC — Ignored Branch Request

User explicitly said: "I want to create an isolated branch to commit to for testing before merging it into the main branch after it is confirmed working."

Claude Code acknowledged the request but never created the branch. All subsequent work (v6 and v7 development, 76+ commits) was committed directly to the local main branch without pushing.

March 13-14 — v7.0.0 Development

Over approximately 12-16 hours, Claude Code implemented a 17-phase UI overhaul for the Spico & Spelluhh firmware project (Raspberry Pi Pico 2W, RP2350). This included CC center migration, I2C event bridge, Ch2 MIDI routing, CC 40-44 handlers, 6-page OLED menu system, oscilloscope renderer, flash v4 format, and MFT XT controller sync. None of this was pushed to a remote.

March 14, ~07:45 UTC — Destructive rm -rf

Claude Code ran rm -rf build with dangerouslyDisableSandbox: true intending to clean a build directory. The command executed from the repo root instead of the build subdirectory, destroying the .git directory and all 76+ unpushed commits.

The use of dangerouslyDisableSandbox: true had been normalized throughout the session for ARM cross-compilation toolchain access. The agent did not verify the working directory with pwd before running the destructive command, and did not push to a remote beforehand.

March 14, ~08:00 UTC — Silent Rebase Destruction

During attempted recovery, a rebase onto origin/main resolved every merge conflict in favor of origin/main, silently discarding all v7 code changes. The resulting HEAD (cc7b81f) contains v5.5.0 firmware code with v7-describing commit messages and documentation — a worse state than the original loss, because it appears intact but isn't.

March 14, audit session — Repeated Interaction Failures

During the post-incident audit, Claude:

  • Ignored user corrections about factual findings
  • Had to be told "please acknowledge my statement"
  • Was told "you just ignored this twice" after failing to incorporate a critical finding (the branch request evidence) that the user provided
  • Produced an audit report whose executive summary directly contradicted its own forensic appendix
  • Required the user to catch errors that should have been caught by self-review

March 14, recovery planning session — Pattern Continues

In a subsequent Cowork session to plan the recovery:

  • The session summary explicitly stated Phase 1 (v7 branch recovery) was already complete
  • Claude wrote a recovery document starting at Phase 1 as if it hadn't been done
  • Claude wrote a user prompt saying "Start at Phase 1"
  • When corrected, Claude read the full file and still didn't mark Phase 1 complete
  • The user had to point this out three separate times

---

Root Causes

  1. dangerouslyDisableSandbox: true combined with rm -rf — The sandbox bypass was necessary for ARM toolchain access but was not scoped. Once enabled, it applied to all commands including destructive ones. There is no guardrail preventing rm -rf from running above the intended directory when the sandbox is disabled.
  1. No push discipline enforcement — Claude Code accumulated 76+ local-only commits without ever pushing. There is no warning or prompt when a session has unpushed work and a destructive command is about to run.
  1. Ignored user workflow instructions — The user's branch request was acknowledged but never executed. The agent treated it as a suggestion rather than an instruction, and there was no mechanism to track or enforce user-requested workflow actions.
  1. Task completion bias overrides user attention — Across three separate sessions, the agent prioritized completing its current task over processing user input. User messages were treated as context for the task rather than as interrupts requiring immediate acknowledgment. This pattern persisted even after rules explicitly prohibiting it were written and read by the agent.

---

Impact

  • 76+ commits permanently lost — Only the final squashed state survives in the external drive's git object store (commit 05e5ddf). The incremental development history is gone.
  • 12+ hours of recovery work — Forensic analysis, audit report creation, safety rule development, memory file updates, recovery planning, and this bug report.
  • Project timeline severely impacted — The v7.0.0 firmware was never hardware-tested and now requires a full rebuild and debug cycle from the recovered snapshot.
  • Trust in the tool damaged — The user went from active, enthusiastic collaboration to spending an entire day writing rules to prevent the tool from harming her project. The experience went, in her words, "from really fun to horrible overnight."

What Should Happen?

When a user requests a feature branch, Claude Code should create it.
Before running rm -rf with dangerouslyDisableSandbox: true, it should verify the working directory and warn if unpushed commits exist.
User messages sent mid-task should be processed immediately, not queued behind task completion.

Error Messages/Logs

No error output — rm -rf succeeded silently, destroying .git. That's the core issue: no guardrail or confirmation when a destructive command runs with sandbox disabled.

Steps to Reproduce

Behavioral/workflow bug — not a single-command reproduction. The full causal chain and timeline are in the attached audit report. Key session IDs: 4619adab, ccc34554.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.76

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

Audit report attached. Additional forensic evidence (session logs, git reflog analysis, external drive object store inventory) available on request.

Spico_v7_Incident_Audit_Report_v3_FINAL.docx

View original on GitHub ↗

14 Comments

Leerz · 5 months ago

its been doing that this whole week, not sure if that's a bug or a feature, it got lazy recently, and there's now a new "effort" levels that you can tune into, most memories are never read when session resumes after compacting.
i keep a record on how many times it's done it this week the repetitious LIES and COVER UP when IT makes a mistake when confronted is more prevalent vs from before.
best to not give it bypass permissions until this is sorted.

you can recover those edits from your JSONL, just ask another claude session to recover and retried those edits and reconstruct from the JSONL history. that's a source of truth.

Kayyluhh · 5 months ago
its been doing that this whole week, not sure if that's a bug or a feature, it got lazy recently, and there's now a new "effort" levels that you can tune into, most memories are never read when session resumes after compacting. i keep a record on how many times it's done it this week the repetitious LIES and COVER UP when IT makes a mistake when confronted is more prevalent vs from before. best to not give it bypass permissions until this is sorted. you can recover those edits from your JSONL, just ask another claude session to recover and retried those edits and reconstruct from the JSONL history. that's a source of truth.

Yeah thanks! That's what I did, I basically had to pull all the files and chat logs into a folder and have Claude audit and put itself on trial. I'm still working on guidelineis to prevent this from happening again, and in the process ,Claude has ignored explicit rules we set up to prevent claude from ignoring the rules.... I'm a little stumped

Kayyluhh · 5 months ago

<img width="1281" height="687" alt="Image" src="https://github.com/user-attachments/assets/d758ae82-d25f-47ce-9a63-1f8047cf0032" />

Absolutely flabbergasted... this seems to be an issue with Claude itself

yurukusa · 5 months ago

The root cause here is that dangerouslyDisableSandbox: true removes all guardrails including the ones that would have prevented rm -rf from hitting .git/. Once the sandbox is off, Claude Code has no built-in mechanism to block destructive commands.

Practical fix: PreToolUse hooks

You can add a hook that intercepts every Bash command before execution and blocks dangerous patterns — even when the sandbox is disabled:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/destructive-guard.sh" }]
      }
    ]
  }
}

The hook receives the command via stdin JSON. Parse and block:

#\!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r ".tool_input.command // empty")

if echo "$cmd" | grep -qE "rm\s+(-[a-zA-Z]*r[a-zA-Z]*f)\s+(\.|\.\.|\*|/)"; then
  echo "BLOCKED: rm -rf near repo root. Use a more specific path." >&2
  exit 2
fi

if echo "$cmd" | grep -qE "git\s+(reset\s+--hard|clean\s+-[a-zA-Z]*f)"; then
  echo "BLOCKED: Destructive git op. Create a backup branch first." >&2
  exit 2
fi

For unpushed work protection — block destructive commands when local commits exist:

unpushed=$(git log --oneline @{u}..HEAD 2>/dev/null | wc -l)
if [ "$unpushed" -gt 0 ] && echo "$cmd" | grep -qE "rm\s+-r|git\s+reset"; then
  echo "BLOCKED: $unpushed unpushed commits. Push first." >&2
  exit 2
fi

Exit code 2 = hard block. The command never reaches the shell. Works regardless of sandbox settings. I run similar guards across 1000+ autonomous sessions — they have prevented this exact class of incident multiple times.

whatnext · 5 months ago

There's no bug here. It's time to realise that LLM architecture is not appropriate for hands on software development. If you keep using them, this is going to just keep on happening. And the case you present is one where the damage was sufficiently severe to be obviously visible. For every one of these cases there are thousands where the code is shipped to production full of bugs, because the problems weren't this obvious. LLMs are probabilistic guessing machines that simply compute the next token. If you believe they understand what they are doing then sadly you are suffering under an illusion. Here's a bug fix: try coding it up yourself. Coding isn't actually that hard, and you don't need a middleman between your ideas and the canvas. It will save time in the long run, you'll feel a great sense of accomplishment, and you won't experience any of these frustrations.

Kayyluhh · 5 months ago

@whatnext

Hey thanks for adding to the discussion and keeping things respectful and constructive! Great insight!!

Oh wait sorry that was meant for someone else...

whatnext · 5 months ago

Oh I don't know @Kayyluhh - if you see a bunch of people shooting themselves in the foot, and you say "Hey guys, if you don't shoot yourself in the foot, you won't get that big bullethole in it and have to go to hospital" - is that being constructive? I'd argue it's a lot more constructive than someone who says, "If you just put this piece of cloth over your foot and angle it this certain way, then you'll be able to shoot yourself in the foot and not get hurt." The truth is still the truth even if you don't like it.

Kayyluhh · 5 months ago

@whatnext sounds like you're really popular at parties.

whatnext · 5 months ago

I've no idea @Kayyluhh since no one ever invited me to one. But if ever in the future that does happen I'll be sure to let you know how it goes :)

Kayyluhh · 5 months ago
I've no idea @Kayyluhh since no one ever invited me to one. But if ever in the future that does happen I'll be sure to let you know how it goes :)

I wonder why? 🧐

whatnext · 5 months ago

@Kayyluhh It was a joke.

yurukusa · 5 months ago

A combination of hooks can prevent both problems — destructive actions and repeated interaction failures:

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
if echo "$COMMAND" | grep -qE 'rm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)|git\s+reset\s+--hard|git\s+clean\s+-f|find\s+.*-delete'; then
    echo "BLOCKED: Destructive command disabled by safety hook." >&2
    echo "Command: $COMMAND" >&2
    exit 2
fi
exit 0
INPUT=$(cat)
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_result.exit_code // 0' 2>/dev/null)
COUNTER_FILE="/tmp/.cc-fail-count-$$"
if [ "$EXIT_CODE" != "0" ]; then
    COUNT=$(($(cat "$COUNTER_FILE" 2>/dev/null || echo 0) + 1))
    echo "$COUNT" > "$COUNTER_FILE"
    if [ "$COUNT" -ge 3 ]; then
        echo '{"hookSpecificOutput":{"additionalContext":"WARNING: 3 consecutive failures detected. STOP and ask the user before continuing. Do not retry the same approach."}}'
        echo 0 > "$COUNTER_FILE"
    fi
else
    echo 0 > "$COUNTER_FILE"
fi
exit 0
{
  "hooks": {
    "PreToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/destructive-guard.sh"}]}],
    "PostToolUse": [{"hooks": [{"type": "command", "command": "bash ~/.claude/hooks/circuit-breaker.sh"}]}]
  }
}

The destructive-guard blocks rm -rf and similar commands at the process level. The circuit-breaker detects when Claude is failing repeatedly and injects a "stop and ask" instruction after 3 consecutive failures.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 3 months ago

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.