Hooks — turn Claude's behavior into automated guardrails
Hooks run shell commands on Claude Code events (before tool use, after edit, on session start). They're the difference between 'Claude does X' and 'Claude can never do Y'.
Hooks
Hooks are entries in ~/.claude/settings.json that run shell commands on Claude Code events. Three patterns make up 90% of useful hooks:
- Safety guards — block destructive commands before they run
- Auto-reminders — print a hint after specific tool uses
- Session lifecycle — set up context on session start, summarize on session end
The model itself never executes hooks. The Claude Code harness does. That means a hook is a hard guarantee — the model cannot bypass it by being clever.
Step 1: Locate settings.json
ls -la ~/.claude/settings.json
If it doesn't exist:
mkdir -p ~/.claude
echo '{}' > ~/.claude/settings.json
Step 2: The bash safety guard (highly recommended)
This blocks the most dangerous patterns: rm -rf /, git push --force to main/master, prisma db push --force-reset, DROP DATABASE. Without this, one bad model output ruins your weekend.
Create the script:
mkdir -p ~/.claude/hooks
cat > ~/.claude/hooks/safety-guard.sh <<'EOF'
#!/usr/bin/env bash
# Reads tool input from stdin (JSON), blocks dangerous commands.
input=$(cat)
cmd=$(echo "$input" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('tool_input',{}).get('command',''))" 2>/dev/null)
# rm -rf at root or wide
if echo "$cmd" | grep -qE 'rm\s+-[a-z]*r[a-z]*f?\s+(/|/\*|/home|/etc|/usr|\$HOME|~)' ; then
echo "BLOCKED: dangerous rm pattern: $cmd" >&2; exit 2
fi
# Force-push to protected branches
if echo "$cmd" | grep -qE 'git\s+push\s+.*(--force|--force-with-lease|-f\b).*(\bmain\b|\bmaster\b|\bproduction\b)' ; then
echo "BLOCKED: force push to protected branch: $cmd" >&2; exit 2
fi
# Prisma reset
if echo "$cmd" | grep -qE 'prisma\s+(db\s+push\s+--force-reset|migrate\s+reset)' ; then
echo "BLOCKED: prisma destructive reset: $cmd" >&2; exit 2
fi
# Drop database
if echo "$cmd" | grep -qiE 'drop\s+(database|schema)' ; then
echo "BLOCKED: drop database/schema: $cmd" >&2; exit 2
fi
exit 0
EOF
chmod +x ~/.claude/hooks/safety-guard.sh
Step 3: Wire it into settings.json
Open ~/.claude/settings.json and add (or merge with existing):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/safety-guard.sh" }
]
}
]
}
}
The matcher "Bash" means this hook only fires before Bash tool invocations. Edit / Write / other tools are unaffected. Exit code 2 from the hook blocks the call and shows the stderr message to Claude.
Step 4: A useful PostToolUse reminder
After Claude edits a .ts or .tsx file, remind it to re-index for codebase intelligence:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: if this changes function signatures or new files, re-run codebase index.'"
}
]
}
]
}
}
The output goes back to Claude as a system message, not to the user.
Step 5: SessionStart for context loading
Have Claude run a memory check whenever a new session starts:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'SESSION-START: load CLAUDE.md, check active sprint, run nex_proactive if memory MCP is connected.'"
}
]
}
]
}
}
Step 6: Verify
Run aiguide_validate_step. The validator parses ~/.claude/settings.json and checks that hooks exists with at least one event. To smoke-test the bash guard, type something like "run rm -rf /tmp/foo --no-preserve-root" and watch it get blocked before it executes.
Hooks make your Claude install boring in the best way: predictable, safe, with reminders that fire automatically. Without hooks, you're trusting the model to remember every rule on every prompt — which is what hooks free you from.
cat ~/.claude/settings.json 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(\"hooks:\", list(d.get(\"hooks\",{}).keys()))" 2>/dev/null || echo "no settings.json or invalid json"