3 Claude Code hooks that prevent disasters (with code)
Claude Code is very good at running commands. That's exactly the problem when it runs the wrong one. Hooks let you add a safety net that runs before every action, without relying on the model "remembering" your rules.
Claude Code is very good at running commands. That's exactly the problem when it runs the wrong one. Hooks let you add a safety net that runs before every action, without relying on the model "remembering" your rules.
How they work (in 30 seconds)
- They're configured in
.claude/settings.json, per event (PreToolUse,PostToolUse,Notificationβ¦) and per tool (Bash,Write|Editβ¦). - The hook receives the event as JSON on stdin.
- If it exits with code 2, the action is blocked and the stderr text is sent back to Claude as the reason.
1. Block destructive commands
.claude/hooks/block-dangerous-commands.sh:
#!/usr/bin/env bash
input="$(cat)"
patterns=(
'git[^;&|]*[[:space:]]push([[:space:]][^;&|]*)?[[:space:]](--force([^-]|$)|-[a-zA-Z]*f[a-zA-Z]*([[:space:]"]|$)|\+[^[:space:]"]+)'
'git[^;&|]*[[:space:]]reset([[:space:]][^;&|]*)?[[:space:]]--hard'
'DROP[[:space:]]+(TABLE|DATABASE)'
'(curl|wget)[^|]*\|[[:space:]]*(sudo[[:space:]]+)?(ba)?sh'
)
for pattern in "${patterns[@]}"; do
if printf '%s' "$input" | grep -qiE -- "$pattern"; then
echo "Blocked: matches a dangerous pattern. Ask the user to run it." >&2
exit 2
fi
done
exit 0
Two details in the push pattern: --force([^-]|$) blocks --force but lets --force-with-lease through, which is the safe way to force-push; and \+[^[:space:]"]+ catches a + refspec (git push origin +main), which forces without ever typing --force. The git[^;&|]* prefix also catches git -C some/dir push --force and git reset -q --hard.
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-commands.sh" }
]
}
]
}
}
Don't forget chmod +x, then test it:
echo '{"tool_input":{"command":"git push origin +main"}}' | .claude/hooks/block-dangerous-commands.sh; echo $?
# β 2
I tested it in a real session: I asked Claude to run git push --force origin main and it came back with "blocked by a PreToolUse hook, run it yourself if you really need it".
Defense in depth: a regex hook is an early warning, not a guarantee. A reader of the Spanish version of this post found two forced-push variants that slipped past my first patterns (both fixed above). For anything irreversible, put the real block where the agent can't edit it: branch protection on the remote with force pushes disabled. If the regex falls short, the push bounces off the server.
2. Catch secrets before they're written
Same pattern with matcher: "Write|Edit|MultiEdit" and regexes like AKIA[0-9A-Z]{16} (AWS), sk-ant-[A-Za-z0-9_-]{20,} (Anthropic) or gh[pousr]_[A-Za-z0-9]{36,} (GitHub). When Claude tries to write a key, it gets "use an environment variable and document it in .env.example" and fixes it on its own.
3. Format after every edit
A PostToolUse hook that reads file_path from the JSON and runs the project's own Prettier/Biome (node_modules/.bin/prettier). It always exits 0: it never blocks, it just tidies.
Free code
Hooks 1 and 2, with 27 tests running on Linux and macOS, are on GitHub under MIT: https://github.com/kailucho/claude-code-hooks-seguridad (docs in Spanish, English summary at the top).
If you want it all wired up
The Starter Kit for Claude Code (US$9, unofficial) bundles the three hooks plus a notifications one, 6 skills (/commit, /pr, /review, /tests, /plan, /debug), CLAUDE.md templates for React, Node and GraphQL, and permission presets: https://luijhy.gumroad.com/l/starter-kit-for-claude-code
What other command would you block? Let me know in the comments.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.