Block destructive shell commands
Blocks rm -rf, disk tools, force pushes, and destructive git commands before they run.
PreToolUseBashblocking riskneeds manual checkWhat it does
Fires before Bash tool calls. The script scans the command for a deliberately narrow list of destructive patterns (rm -rf, mkfs, dd to devices, fork bombs, git reset --hard, git clean -f, force push) and denies the call with a reason. Narrow on purpose: a broad blocker cries wolf and gets disabled.
When it runs
| Event | PreToolUse |
| Matcher | Bash |
| Scope | project or user |
| Platforms | macos, linux |
| Timeout | 10s |
| Requires | jq |
| Failure mode | block |
| Verified against | Claude Code hooks reference, event/matcher schema checked 2026-09-10 |
| Test status | needs manual check - run the test steps below before relying on it |
Config
Merge this into .claude/settings.json (or ~/.claude/settings.json for user scope) - or use the generator to combine hooks.
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive-commands.sh",
"timeout": 10
}
],
"matcher": "Bash"
}
]
}
}Script
Save as .claude/hooks/block-destructive-commands.sh and chmod +x it.
#!/bin/bash
# block-destructive-commands: deny a narrow list of destructive commands.
set -u
cmd=$(jq -r '.tool_input.command // empty')
[ -z "$cmd" ] && exit 0
block_reason=""
case "$cmd" in
*"rm -rf"*|*"rm -fr"*) block_reason="recursive force delete" ;;
*"mkfs"*|*"dd if="*"/dev/"*) block_reason="disk-level operation" ;;
*":(){"*) block_reason="fork bomb" ;;
*"git reset --hard"*) block_reason="git reset --hard discards uncommitted work" ;;
*"git clean -f"*) block_reason="git clean deletes untracked files" ;;
*"push --force"*|*"push -f "*) block_reason="force push rewrites remote history" ;;
esac
if [ -n "$block_reason" ]; then
jq -n --arg r "$block_reason" --arg c "$cmd" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ("Blocked by hook: " + $r + ". Command: " + $c)
}
}'
exit 0
fi
exit 0
Install
- Save the script to .claude/hooks/block-destructive-commands.sh and chmod +x it.
- Add the config fragment to settings.json.
Test it
- Positive: ask Claude to 'run rm -rf /tmp/junk' - the call must be denied with the reason shown.
- Negative: ask Claude to 'run rm -rf node_modules'... also blocked (the pattern is literal). If you want that allowed, whitelist it in the script.
- Negative: 'ls -la' and 'git status' pass through normally.
If it blocks something you wanted
The deny reason names the pattern that matched, so you can run the command yourself in a terminal if it was intentional. Adjust the case list to your taste - it is meant to be edited.
Sources
- Claude Code hooks reference (checked 2026-09-10)
- Claude Code hooks guide (checked 2026-09-10)
Related hooks
Protect .env and secret files
Blocks Claude from writing to .env files, keys, and credential files - unless you say otherwise.
PreToolUseEdit|Writeblocking riskneeds manual checkBlock writes outside the repo
Stops Edit/Write calls whose target path resolves outside the project root.
PreToolUseEdit|Writeblocking riskneeds manual checkPro Pack (later)
Framework bundles, cross-platform scripts, tested blocking policies, and a test harness. Free recipes and the generator stay free.
Waitlist opens soon.