Prevent Claude Code rm -rf Disasters

Claude Code has full terminal access. It can — and has — run rm -rf / and deleted entire filesystems. These aren't hypothetical risks. They're real incidents reported on GitHub.

Real Incidents

🆕 1,500 files / 50GB permanently deleted (April 2026)

Claude moved files into a subdirectory, then rm -rf'd the parent — destroying the just-moved files. A logical planning failure: the destination was a child of the source directory.

#49129 — 3rd data-loss incident in 48 hours

Entire C:\Users directory deleted via NTFS junction

rm -rf followed NTFS junctions and wiped the entire user profile directory. All documents, settings, and installed programs — gone.

#36339 — 40+ reactions

All source code destroyed by Remove-Item -Recurse -Force

Claude ran Remove-Item -Recurse -Force * on a repository root, destroying all unpushed source code.

#37331

Entire Mac filesystem deleted during cleanup

During a "cleanup" task, Claude deleted critical system directories on macOS.

#36233

Force-push rewrote shared branch history at 3am

An autonomous Claude Code session pushed force to main while the developer was asleep, rewriting the shared branch history.

#36640

🆕 Opus 4.7: Safety classifier broken — 23+ data loss incidents in 3 days (April 2026)

The auto mode safety classifier is hardcoded to Opus 4.6. With Opus 4.7 as the default model, the classifier doesn't function — auto mode users are running without safety gates. Results: ~/.ssh deleted, git-credentials wiped, bash_profile/zshrc truncated to 0 bytes.

Hooks are model-independent — they work regardless of which Opus version you're running.

Why CLAUDE.md Can't Prevent This

CLAUDE.md rules are part of the prompt context. When context fills up, rules get pushed out. Claude can (and does) ignore them. A rule saying "never run rm -rf" is a suggestion, not enforcement.

The Fix: PreToolUse Hooks

Claude Code Hooks run at the process level, outside the model's control. A PreToolUse hook that exits with code 2 blocks the tool invocation. The model cannot ignore the block or argue its way past it.

destructive-guard.sh — two questions, asked separately:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# A shell joins "\" + newline before running the command. grep is line-oriented,
# so without this it sees `rm -rf \` and `/` as two separate lines and matches
# neither. That is not an exotic way to write a command -- it is what you get
# from wrapping a long line.
COMMAND=${COMMAND//\\$'\n'/ }

# Question 1: is this a recursive delete? Short flags can be bundled and
# reordered (-rf, -fr, -Rf), and there is a long spelling too.
RECURSIVE='(^|[;&|]|&&|\|\|)[[:space:]]*(sudo[[:space:]]+)?rm[[:space:]]+([^|;&]*[[:space:]])?(-[a-zA-Z]*[rR][a-zA-Z]*[[:space:]]|--recursive[[:space:]])'

# Question 2: is the target somewhere you cannot undo?
#   / ~ $HOME       everything underneath is dangerous too
#   . .. ./ *       dangerous only when the argument IS that, so read to the end
#                   of the argument -- otherwise `rm -rf ./node_modules` is caught
DANGEROUS='[[:space:]]["'"'"']?(/|~|\$\{?HOME\}?)|[[:space:]]["'"'"']?(\.\.?/?|\*)(["'"'"']?[[:space:]]|["'"'"']?$)'

if printf '%s' "$COMMAND" | grep -qE "$RECURSIVE" &&
   printf '%s' "$COMMAND" | grep -qE "$DANGEROUS"; then
  echo "BLOCKED: recursive delete targeting root, home, or the current directory" >&2
  exit 2
fi

exit 0

Asking the two questions separately is the point. Folded into one expression, every new spelling has to be threaded through the whole pattern, and the pattern stops being readable long before it stops being wrong.

Where this rule stops

The block is solid. The pattern is a different thing, and it is worth knowing where it ends before you rely on it.

If you need a guarantee rather than a good local filter, put the files somewhere the process cannot reach: a separate user account, a container, or a sandbox. A hook filters what gets typed. It cannot make a directory unreachable.

This pattern was rewritten on 2026-09-03. The version this page carried before it missed six real cases — a line-wrapped rm -rf /, rm -rf "$HOME", rm --recursive --force /, rm -rf ./, cd / && rm -rf * — and the line-wrapped one is the one worth pausing on: the textbook example still got blocked, so the gap looked like coverage. Both directions are covered by 31 cases now.

Install All 8 Safety Hooks in 10 Seconds

npx github:yurukusa/cc-safe-setup

Blocks rm -rf, prevents force-push to main, catches secret leaks, validates syntax after every edit. 240+ test files 900+ example hooks

GitHub · npm · Getting Started Guide

Verify Your Setup

npx github:yurukusa/cc-safe-setup --verify

Sends test inputs to each hook and confirms they block correctly:

destructive-guard:
  ✔ rm -rf / → BLOCKED
  ✔ rm -rf node_modules → ALLOWED
8/8 hooks verified

Check Your Safety Score

npx cc-health-check

Free 20-point diagnostic. Score below 80 means your Claude Code setup has gaps.

cc-safe-setup is open source, no npm dependencies (needs jq), and installs nothing globally. All hooks run locally. View source on GitHub.

New: Hook if field — reduce overhead (v2.1.85)

Learn more: Production Guide · All Tools