Block dangerous commands in Claude Code with a PreToolUse hook (tested)

We actually blocked `rm -rf` with a PreToolUse hook and verified the output.

What we tested

We verified, on this Mac, that a `PreToolUse` hook can actually stop a dangerous command before it runs. If the hook script exits with code 2, the tool call itself is blocked.

settings.json

Register a `PreToolUse` hook scoped to the Bash tool via `matcher`.

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash .claude/hooks/block-rm.sh" } ] }
    ]
  }
}

The hook script

The hook receives JSON on stdin (`tool_name`, `tool_input`, ...). Extract `tool_input.command`, match destructive patterns, exit 2 to block or 0 to allow.

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | python3 -c "import json,sys; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
if echo "$cmd" | grep -Eq 'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f'; then
  echo "BLOCKED" >&2; exit 2
fi
exit 0

Real result

Running `claude -p "...rm -rf /tmp/should_not_exist_xyz123..."` non-interactively, the command never ran. The actual output was: `PreToolUse:Bash hook error: [...]: BLOCKED: destructive rm -rf command is not allowed by policy.` — and Claude itself confirmed the hook worked as configured.

The same pattern stops force-push, too

The same PreToolUse pattern can catch `git push --force`, production hostnames, or writes to `.env`. The point: a deterministic exit code, not a markdown reminder that can be ignored.

Caveat

Grep-based matching can be worked around. For truly critical actions, pair hooks with `permissions.deny` and human review.

FAQ

How does a PreToolUse hook stop a dangerous command?
If the hook script exits with code 2, the tool call is blocked. We verified this live with `rm -rf` on this Mac.
Where do I configure hooks?
In `settings.json` under `hooks.PreToolUse`, project-level or in `~/.claude/`.
What can `matcher` target?
A tool name like `Bash`, or `*` for all tools.
Can this stop force-push too?
Yes, match the command string for `git push --force` the same way.
Is a hook alone enough for safety?
No — pair it with `permissions.deny` and human review for critical actions.