Claude Code Hooks: 6 Copy-Paste Examples (Block rm -rf, Protect .env, Auto-Format)
Published Oct 15, 2026 · 7 min read · Checked against the official hooks reference
Hooks are the only part of Claude Code that are guaranteed to run. CLAUDE.md is advice and skills are instructions. A hook runs every time its event fires, whatever the model decides. If a rule must never be broken, it belongs in a hook.
How hooks work in 30 seconds
- You define them in
.claude/settings.json(project, committed),.claude/settings.local.json(just you) or~/.claude/settings.json(all projects). - Structure: event (e.g.
PreToolUse) → matcher (tool name such asBashorEdit|Write) → handlers (usually"type": "command"). - Your script receives JSON on stdin with
tool_nameandtool_input. - Exit 2 blocks the action (on events that can block), and stderr is shown to Claude. Exit 0 means no objection. Other exit codes are non-blocking errors. Exit 1 does not block, which catches a lot of people out.
- Use
${CLAUDE_PROJECT_DIR}in paths, and run/hooksto see what's loaded.
1. Block destructive shell commands
#!/usr/bin/env bash
# .claude/hooks/block-dangerous.sh
cmd=$(jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -Eq 'rm -rf (/|~|\*)|git push (-f|--force)|git reset --hard|mkfs|dd if='; then
echo "Blocked: '$cmd' looks destructive. Ask the user to run it manually." >&2
exit 2
fi
exit 0
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
{ "type": "command", "command": "\"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/block-dangerous.sh" } ] } ] } }
2. Protect .env, lockfiles and .git
#!/usr/bin/env bash
# .claude/hooks/protect-files.sh (matcher: "Edit|Write")
path=$(jq -r '.tool_input.file_path // ""')
case "$path" in
*.env|*.env.*|*/.git/*|*package-lock.json|*pnpm-lock.yaml|*yarn.lock)
echo "Blocked: $path is protected." >&2; exit 2;;
esac
exit 0
3. Auto-format after every edit
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write", "timeout": 30 } ] } ] } }
PostToolUse can't block, because the edit already happened. Its job is cleanup.
4. Don't let Claude stop until tests pass
#!/usr/bin/env bash
# Stop hook: exit 2 sends Claude back to work with this message
if ! npm test --silent >/tmp/cc-test.log 2>&1; then
echo "Tests are failing. Fix them before finishing:" >&2; tail -20 /tmp/cc-test.log >&2; exit 2
fi
exit 0
Register it under "Stop" (no matcher). Guard against infinite loops: the input includes stop_hook_active, so exit 0 when it's true.
5. Desktop ping when Claude needs you
{ "hooks": { "Notification": [ { "hooks": [
{ "type": "command", "command": "notify-send 'Claude Code' 'Needs your input'" } ] } ] } }
On macOS, use osascript -e 'display notification "Needs input" with title "Claude Code"'.
6. Inject live context at session start
{ "hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [
{ "type": "command", "command": "echo \"Branch: $(git branch --show-current). Open TODOs: $(grep -rc TODO src | awk -F: '{s+=$2} END {print s}')\"" } ] } ] } }
Plain stdout from a SessionStart hook is added to Claude's context.
Gotchas
- Make scripts executable (
chmod +x) and installjq. - A regex blocklist is a seatbelt, not a sandbox. Pair it with permission
denyrules. - Print only JSON to stdout if you return JSON. Shell-profile noise breaks parsing.
- Debug with
claude --debug.
Get hooks 1 and 2 free, pre-tested
The Claude Code Safety Hooks Mini-Pack includes both scripts, a settings.json and a test script.
Want the full setup with subagents, skills and CLAUDE.md templates? Claude Code Agent Starter Kit, $39.
Reference: code.claude.com/docs/en/hooks
FAQ
Where are Claude Code hooks configured?
In the hooks section of a settings file: .claude/settings.json for the project, .claude/settings.local.json for personal overrides, or ~/.claude/settings.json for every project. Run /hooks to see what is active.
How does a hook block a command?
A PreToolUse hook receives the tool call as JSON on stdin. If the script exits with code 2, Claude Code blocks the call and shows the script's stderr to Claude. Exit code 0 allows it; other non-zero codes are treated as non-blocking errors.
Can hooks protect my .env file?
Yes. A PreToolUse hook matched on Edit and Write can check the file path and exit 2 for .env files, lockfiles and the .git folder. The free VibeKit safety hooks include this hook ready to use.
Do hooks work on Windows?
Hooks run shell commands, so bash-based hook scripts need WSL or Git Bash on Windows.