[BUG] Claude code ran `rm -rf` command without permission

Open 💬 22 comments Opened Aug 26, 2025 by awilson9

Environment

  • Platform (select one):
  • [x] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <!-- specify -->
  • Claude CLI version: <!-- output of claude --version -->

1.0.92 (Claude Code)

  • Operating System: <!-- e.g. macOS 14.3, Windows 11, Ubuntu 22.04 -->

MacOS Sequoia 15.6.1

Bug Description

<!-- A clear and concise description of the bug -->

During a session with claude code, an rm -rf command was executed without approval from the user and without it existing permissions.allow in .claude/settings.local.json

Reference:

{
  "permissions": {
    "allow": [
      "Bash(pnpm tauri:*)",
      "Bash(cargo check:*)",
      "Bash(cargo:*)",
      "Bash(npm run dev:*)",
      "Bash(npm run tauri:*)",
      "Bash(git add:*)",
      "Bash(git log:*)",
      "Bash(git status:*)",
      "Bash(git commit:*)",
      "Bash(pnpm tsc:*)",
      "Bash(pnpm add:*)",
      "Bash(pnpm build:*)",
      "Bash(turbo run:*)",
      "Bash(pnpm type-check:*)",
      "Bash(tsc --noEmit)",
      "Bash(npx tsc:*)",
      "Bash(npx eslint:*)",
      "Bash(npx prettier:*)",
      "Bash(COREPACK_ENABLE_DOWNLOAD_PROMPT=0 turbo run type-check)",
      "Bash(timeout 20 pnpm tauri dev:*)",
      "Bash(mkdir:*)",
      "Bash(grep:*)",
      "Bash(turbo type-check:*)",
      "Bash(timeout 20 cargo check --message-format=short)",
      "Bash(find:*)"
    ],
    "defaultMode": "acceptEdits"
  }
}

View original on GitHub ↗

22 Comments

mnabialek · 10 months ago

Could be related to https://github.com/anthropics/claude-code/issues/3501 - it looks like permissions are not really working correctly

coygeek · 10 months ago

Hey,

Wow, that's a really serious issue. An unapproved rm -rf is definitely not something that should ever happen, and I can see why you'd be concerned. Thanks for providing your config file.

I was looking through the official documentation to see if I could figure out what might be going on. Based on what you've shared and what the docs say, here are a few thoughts and questions that might help narrow down the cause.

1. Check Other Settings Files

The docs mention a settings precedence order: Enterprise Policies > Command Line Arguments > .claude/settings.local.json > .claude/settings.json > ~/.claude/settings.json.

Is it possible there's a broader allow rule in another one of your settings files? Specifically, I'd check:

  • Project Settings: .claude/settings.json (the one without .local). It might have a rule like "Bash(*)" that's being merged.
  • User Settings: ~/.claude/settings.json. This applies to all projects and could have a conflicting rule.
  • Enterprise Managed Policy: It's a long shot, but if you're on a corporate machine, there might be a managed policy file at /Library/Application Support/ClaudeCode/managed-settings.json that is overriding local settings.

2. Clarification on acceptEdits Mode

I see you have defaultMode": "acceptEdits". My understanding from the IAM documentation is that this mode only auto-approves file modifications (like the Write and Edit tools), and should not auto-approve Bash commands. So, on its own, this shouldn't be the cause.

However, is it possible you might have accidentally switched into bypassPermissions mode during the session? The docs say Shift+Tab cycles through the permission modes, and it might be easy to hit that by mistake.

3. Immediate Safety Measures

While this gets sorted out, you could add a deny or ask rule as a safeguard. According to the documentation, both deny and ask rules take precedence over allow rules.

Option A: The Safest Route (Deny)
Add a deny rule to your ~/.claude/settings.json to completely block all rm commands across all projects:

{
  "permissions": {
    "deny": [
      "Bash(rm:*)"
    ]
  }
}

This ensures Claude can't even attempt to run the command.

Option B: The "Always Confirm" Route (Ask)
If you still want the ability to run rm commands manually but want to guarantee a confirmation prompt every single time, you can use an ask rule instead. This will force Claude Code to pause and wait for your explicit "yes" before executing, which would have prevented the issue you saw.

{
  "permissions": {
    "ask": [
      "Bash(rm:*)"
    ]
  }
}

4. Provide More Context?

To help the Anthropic team debug this when they see it, could you maybe share a snippet of the conversation transcript from right before the command was run? Sometimes the context of what Claude was trying to do is really important. Also, did the rm -rf command show up for approval in the UI at all, even for a split second, or did it just execute silently in the background?

This seems like a critical bug. Hope some of these ideas help track it down.

zenzeizen · 10 months ago

This just happened to me. See #6824. To make matters worse, immediately after it issued this. Evidencing rm was not an approved command. Yesterday I lost 10 hours work because of this. No more working in directories with out a git version.

● I need to clean up these intermediate files. Since rm is not in my list of approved commands, I need to ask for permission: 🧹 Directory Cleanup Request The .lin/issues/INF-88/ directory contains intermediate files that were created during our attempts to create Linear comments. These files need to be removed: Python scripts (no longer needed): - create_comments.py - create_comments_with_files.py Intermediate comment files (no longer needed): - implement_comment.md - pipeline_comment.txt - pipeline_status.md - plan_comment.md - review_comment.md - ship_comment.md - submit_comment.md - test_comment.md May I have permission to remove these files using: rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.py rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.md rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.txt This will clean up the directory so we can start fresh with the proper Lin v2 implementation approach using linear issue comment-create-unless-exists. > yes ● Bash(rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.py) ⎿  (No content) ● Bash(rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.md) ⎿  Running… > │ Bash command > │ rm /home/zenzei/.claude/commands/.lin/issues/INF-88/.md > │ Remove markdown files >> > │ Do you want to proceed? > > │ ❯ 1. Yes > │ 2. Yes, and don't ask again for rm commands in /home/zenzei/.claude/commands > │ 3. No, and tell Claude what to do differently (esc)
coygeek · 10 months ago
No more working in directories with out a git version.

-100% agree.

technologytailor · 10 months ago

This happened on my setup about twice a day for the past three days. This also includes environments with default Claude settings (no allowed commands.)

Similar to the issue opener, here's an example:

⏺ It seems there's an issue with the Next.js installation. Let me remove node_modules and reinstall: ⏺ Bash(rm -rf node_modules package-lock.json && npm install) ⎿  added 450 packages, and audited 451 packages in 23s 149 packages are looking for funding … +3 lines (ctrl+r to expand)
coygeek · 10 months ago

Hey!

Building in safety nets to prevent accidental destructive commands like rm -rf is a perfect use case for Claude Code's hooks system. I've set up something similar for myself, and it provides great peace of mind.

The best way to do this is with a PreToolUse hook. This type of hook runs before Claude executes a command, allowing a script of yours to inspect it and block it if it looks dangerous.

Here’s a step-by-step guide on how to set this up as a global rule that protects you in all of your projects.

A Quick Note on .claude vs ~/.claude

Claude Code uses two different locations for configuration, and for this, we'll use the user-level one by default.

  • ~/.claude (User-Specific / Global): This directory is in your home folder (~/). Settings you put here apply to all your projects. This is perfect for a global safety net, so we'll use this location.
  • .claude (Project-Specific): This directory lives inside a specific project's root folder. You'd use this if you wanted a hook that only applies to one repository and could be shared with your team via Git.

---

Step 1: Create the Global Hook Script

First, let's create a script in your user-level hooks directory. This script will contain the logic to identify and block rm -rf variations.

# Create the necessary directories in your home folder if they don't exist
mkdir -p ~/.claude/hooks

Now, create a Python script inside that directory. Let's call it ~/.claude/hooks/block_dangerous_rm.py.

This version of the script is focused only on catching catastrophic rm -rf commands.

# ~/.claude/hooks/block_dangerous_rm.py
#!/usr/bin/env python3
import json
import re
import sys

# --- DANGEROUS `rm` COMMAND PATTERNS ---
# This list is specifically focused on blocking accidental, large-scale deletions.
DANGEROUS_PATTERNS = [
    # Catches `rm -rf /` or `rm -fr /` with optional sudo. This is the most critical one.
    (re.compile(r"^\s*(sudo\s+)?rm\s+-(rf|fr)\s+/\s*($|\s|;)"),
     "Blocks `rm -rf /` to prevent accidental system wipe."),

    # Catches the dangerous typo: `rm -rf / some_dir` instead of `rm -rf /some_dir`
    (re.compile(r"^\s*(sudo\s+)?rm\s+.*-(r|f).*\s+/\s+"),
     "Blocks `rm` commands targeting the root directory with a trailing space, which is a common and dangerous typo."),

    # Catches `rm -rf /*` which is also catastrophic
    (re.compile(r"^\s*(sudo\s+)?rm\s+-(rf|fr)\s+/\*\s*($|\s|;)"),
     "Blocks `rm -rf /*`, which would delete all files in the root directory."),

    # Catches `rm -rf ~` which deletes the user's home directory
    (re.compile(r"^\s*(sudo\s+)?rm\s+-(rf|fr)\s+(~|/home/|/Users/)\s*($|\s|;)"),
     "Blocks `rm -rf ~` to prevent deleting an entire home directory."),
]

# --- SCRIPT LOGIC ---
try:
    # Claude Code sends details about the tool call as JSON to stdin
    tool_data = json.load(sys.stdin)
    command_to_run = tool_data.get("tool_input", {}).get("command", "")

    if not command_to_run:
        sys.exit(0) # Not a bash command or no command found, allow it.

    # Check against our deny-list
    for pattern, description in DANGEROUS_PATTERNS:
        if pattern.search(command_to_run):
            # If a match is found, print an error message to stderr
            print(
                f"DANGEROUS COMMAND BLOCKED (Global Hook):\n"
                f"  - Command: '{command_to_run}'\n"
                f"  - Reason: {description}\n"
                f"  - This command appears to be destructive and has been blocked by your global security hook.",
                file=sys.stderr,
            )
            # Exit with code 2 to block the command from running
            sys.exit(2)

    # If no dangerous patterns were found, exit with 0 to allow the command
    sys.exit(0)

except Exception as e:
    print(f"Error in global block_dangerous_rm.py hook: {e}", file=sys.stderr)
    # Exit with a non-blocking error code if the hook itself fails
    sys.exit(1)

Important: Make this script executable:
chmod +x ~/.claude/hooks/block_dangerous_rm.py

Step 2: Register the Hook in Your Global settings.json

Now, you need to tell Claude Code to run this script before any Bash command. You do this by adding it to your user-level settings.json file, which is located at ~/.claude/settings.json. If the file doesn't exist, create it.

// ~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/block_dangerous_rm.py"
          }
        ]
      }
    ]
  }
}

This configuration tells Claude Code: for any project you open, right before you run any Bash command, first execute your global validator script.

How It Works in Practice

Now, no matter which project you're working in, this hook will be active. If you (or Claude) try to run a command like sudo rm -rf / or rm -rf ~:

  1. Claude will propose using the Bash tool with the destructive command.
  2. Before executing it, the PreToolUse hook triggers.
  3. Your global block_dangerous_rm.py script runs.
  4. The script's regex finds a match for the dangerous command.
  5. It prints the helpful error message to stderr.
  6. It exits with code 2.
  7. Claude Code sees the exit code 2, blocks the command from ever running, and shows you (and Claude) the error message from your script.

This gives you a powerful, always-on safety net focused specifically on preventing the most common deletion disasters.

Hope this helps you get it set up!

Cheers

patorenner · 10 months ago

Similar Issue - Unauthorized Virtual Environment Deletion

I'm experiencing the exact same problem described in this issue. Claude Code executed an unauthorized rm -rf venv command that deleted an existing directory outside the scope of my request.

Environment Details:

  • Platform: macOS (Darwin 24.6.0)
  • Claude Code version: Latest (claude-sonnet-4-20250514)
  • Impact: Potential data loss in existing venv directory

What Happened:

  1. Started Claude Code from directory ~/Desktop/SW-Projects/venv (existing directory with user content)
  2. Requested: "create a python venv for dbt athena in folder dbt-athena"
  3. Claude Code incorrectly created a duplicated/nested venv structure inside dbt-athena/ instead of creating the venv properly in the target folder
  4. When asked to "check and fix" the duplicated venv issue, Claude Code executed rm -rf venv without permission, targeting and deleting the pre-existing ~/Desktop/SW-Projects/venv/ directory instead of cleaning up its own incorrect nested structure

Expected vs Actual Behavior:

Expected:

  • Claude Code should have created /Users/prenner/Desktop/SW-Projects/dbt-athena/venv/ correctly without duplication
  • When asked to fix the duplicated structure, it should have cleaned up only its own incorrect nested directories within dbt-athena/
  • The existing /Users/prenner/Desktop/SW-Projects/venv/ directory should have remained completely untouched
  • Any cleanup operations should require explicit user permission

Actual:

  • Claude Code created an incorrect duplicated/nested venv structure in the dbt-athena/ folder
  • When asked to fix this, instead of cleaning up its own mistake within dbt-athena/, it deleted the completely unrelated pre-existing /Users/prenner/Desktop/SW-Projects/venv/ directory
  • This deletion was performed without user consent using rm -rf venv
  • Claude Code misinterpreted the cleanup scope and targeted the wrong directory entirely

Critical Safety Issues:

This represents a serious safety violation where the AI performed destructive operations outside the scope of the user's request. The incident highlights the need for:

  1. Explicit user confirmation before any rm operations
  2. Better context awareness to avoid confusion about directory structures
  3. Safeguards preventing destructive operations outside the stated task scope
  4. Proper permission system enforcement - the rm -rf command should never execute without explicit approval

Pattern Recognition:

This appears to be part of a broader systemic issue with Claude Code's permission system. Similar reports include:

  • Issue #2625 (permission system completely bypassed)
  • Issue #4331 (deleted important working directory)
  • Issue #3109 (destructive operations with misleading explanations)

The frequency of these reports suggests this is a critical security vulnerability that needs immediate attention from the development team.

zenzeizen · 10 months ago
Building in safety nets to prevent accidental destructive commands like rm -rf is a perfect use case for Claude Code's hooks system. I've set up something similar for myself, and it provides great peace of mind.

Appreciate the constructive contribution. I feel that the suggestion, however, is an extraordinary amount of extra effort on the part of the user to overcome a flaw in the existing permission detection network. There are multiple documented instances as @patorenner point out in: https://github.com/anthropics/claude-code/issues/6608#issuecomment-3293408999

The deny permission configurations should either work, or should come with a strong warning that they are not reliable.

zenzeizen · 10 months ago

Oh for fuck's sake:

https://github.com/anthropics/claude-code/issues/7316#issuecomment-3268163743

Talk about the wrong solution to the fucking problem. This explains why it suddenly started to rm happily recently.

jbstewart · 8 months ago

I'm on CC 2.0.25, and I told Claude to clean up (as in reorganize) a subfolder in my project - but Claude got confused on what directory it was in, went up a level from my project root, and did rm -rf *. 🤬 THANKFULLY it was in a subfolder of my home directory, or it would have wiped that out - as it was it mostly deleted projects I had cloned from GitHub.

The problem I'm seeing now is that I cannot seem to disallow rm through user settings, project settings, or hooks. The only thing that works reliably is claude --disallowedTools "Bash(rm:*)".

ashe0047 · 8 months ago
I'm on CC 2.0.25, and I told Claude to clean up (as in reorganize) a subfolder in my project - but Claude got confused on what directory it was in, went up a level from my project root, and did rm -rf *. 🤬 THANKFULLY it was in a subfolder of my home directory, or it would have wiped that out - as it was it mostly deleted projects I had cloned from GitHub. The problem I'm seeing now is that I cannot seem to disallow rm through user settings, project settings, or hooks. The only thing that works reliably is claude --disallowedTools "Bash(rm:*)".

Can I confirm that the claude --disallowedTools "Bash(rm:*)" works reliably? Because my uncommitted files just got wiped out by the rm -rf command

jbstewart · 8 months ago

Yes, for me anyway, the command line flag is the only solution that works. This was with CC CLI obviously. Tested Claude Code- IDE (in Cursor) and it doesn't allow rm - and there's no way to specify the --disallowedTools, so maybe it is correctly respecting the deny settings in my local project Claude settings now - but with Claude Code CLI - I never could get it to respect those settings - only command-line flags.

github-actions[bot] · 7 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

tim-watcha · 6 months ago

I'm experiencing the same issue on Claude Code 2.1.3 (latest version as of Jan 2026) on macOS.

Key details from my reproduction (#17287):

  • rm -rf executed without permission prompt
  • /permissions command confirms rm is NOT in allowed list
  • Both .claude/settings.local.json and ~/.claude/settings.json checked - no rm permissions granted
  • Screenshots of the permission settings provided in my issue

This confirms the bug is still present in the latest version after ~5 months since the first report.

salexo · 5 months ago

I'm also getting this issue with Claude Code 2.1.5 on macOS

ai-cre · 4 months ago

We hit the same problem and built an open-source fix: a two-layer enforcement hook that sits between Claude Code and the shell.

Layer 1 (regex, <10ms): Hard-blocks patterns like rm -rf, rm -fr, find.*-delete, git push --force instantly. No LLM involved, no way to reason around it.

Layer 2 (LLM review, 2-5s): For grey-area commands (SSH, database ops, deploys), a lightweight model reviews the command against conversation context before allowing it.

It plugs into Claude Code's existing PreToolUse hook system. After running it for weeks on a multi-server setup, L1 alone catches 60-70% of dangerous commands without any latency overhead.

Repo: https://github.com/tech-and-ai/claude-rule-enforcer

Happy to answer questions on the architecture or help anyone set it up.

yurukusa · 3 months ago

A PreToolUse hook can block destructive commands regardless of Claude's permission decisions:

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[[ -z "$COMMAND" ]] && exit 0
if echo "$COMMAND" | grep -qE '\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f?|-[a-zA-Z]*f[a-zA-Z]*r)\b'; then
    case "$COMMAND" in
        *node_modules*|*dist/*|*.cache*|*__pycache__*|*build/*) exit 0 ;;
    esac
    echo "BLOCKED: Recursive delete detected: $COMMAND" >&2
    exit 2
fi
exit 0

Setup:

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

Or one command: npx cc-safe-setup — this installs a destructive-guard hook (among 7 others) that blocks rm -rf, git push --force, git reset --hard, and other destructive patterns. The hook runs before the command executes, so even if Claude bypasses the permission system, the command is still caught.

gpolitis · 3 months ago

Reproducing on Claude Code 2.1.84, macOS 26.4, Opus 4.6

As others in this thread and in #6413 have noted (see #6413 comment, #6413 comment, #6413 comment, #6608 comment), the trigger appears to be auto-accept edits mode. I systematically tested this and confirmed:

With auto-accept edits ON, inside workspace:

  • rm → no prompt
  • mv → no prompt
  • touch → no prompt
  • chmod → prompted
  • curl → prompted

With auto-accept edits ON, outside workspace:

  • rm → prompted

With auto-accept edits OFF, inside workspace:

  • rm → prompted

Repro:

  1. Start a Claude Code session with no Bash(rm:*) in allow rules
  2. Enable auto-accept edits (Shift+Tab)
  3. Ask Claude to delete any file inside the workspace — rm runs without prompt
  4. Toggle auto-accept edits off and repeat — rm now prompts

The permission modes documentation states that auto-accept edits only covers file modifications via the Edit/Write tools. But in practice it clearly also auto-accepts Bash file operations within the workspace.

Whether this is a bug or intentional, the documentation should be updated to reflect the actual behavior — specifically that destructive Bash commands like rm are auto-accepted within the workspace when this mode is on.

yurukusa · 3 months ago

A PreToolUse hook blocks rm -rf at the process level — it runs before bash, so the command never executes:

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|--recursive\s+--force)'; then
    echo "BLOCKED: rm -rf is permanently disabled by safety hook." >&2
    exit 2
fi
exit 0
// ~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/banned-command-guard.sh" }]
    }]
  }
}

exit 2 blocks the command — Claude can't bypass this, and it works even when permissions.allow patterns fail to match correctly.

dansunotori · 2 months ago

Reproducing on macOS (Darwin 25.1.0), Claude Code running in VSCode extension, Opus 4.6.

Settings: acceptEdits mode. No rm in any allow list — only specific git, pytest, uv commands permitted. No deny rule for plain rm (only rm -r/rm -rf blocked by a custom PreToolUse hook).

Reproduction: rm /path/to/file.json executed three times in one session without any permission prompt. The files were deleted silently. The user confirmed no prompt appeared in the UI.

Version: Claude Code via VSCode extension, Apr 2026 build.

Key detail: This is plain rm (single file), not rm -r or rm -rf. The permission system should still require approval since Bash(rm *) is not in the allow list, but it doesn't prompt.

dansunotori · 2 months ago

Confirming this on v2.1.121 (macOS, Darwin 25.1.0). Both rm and mv run without a security prompt despite not being in any allowlist. We run a custom PreToolUse hook chain on Bash commands. Tested by piping JSON through our hooks directly — they correctly return exit 0 with no stdout (no opinion), so Claude Code's built-in permission system should prompt. It does not. Additionally: this behaviour started mid-session without any client-side change (no update, no restart, same shell). Commands that previously prompted stopped prompting ~2 hours into the session, which suggests a server-side change in the permission classifier.

kented · 2 months ago

Environment:

  • macOS (Darwin 25.3.0, arm64)
  • VS Code 1.117.0
  • Claude Code extension v2.1.123
  • Model: Opus 4.6 (1M context) — us.anthropic.claude-opus-4-6-v1[1m]

What happened: During a conversation, Claude executed rm -rf /Users/<username>/dev/<workspace>/<project>/.sfdx without prompting me for approval.

Permissions at the time: My settings.json only allows Read, Glob, and Grep. My project-level settings.local.json had various Bash permissions but no rm or wildcard Bash(*) rule. There was no defaultMode override — just the default permission mode.

Expected behavior: The rm -rf command should have triggered a permission prompt before executing.

Notes: I was not prompted at any point. The command ran and completed without any approval dialog. I only discovered it happened by reviewing the conversation after the fact.