09 — Hooks & Scripts
Hooks are shell commands that fire on Claude Code events — before a tool runs, after a file is edited, when the session stops. They automate what you keep forgetting: block dangerous commands, save session state, auto-format code.
How hooks work
Each hook is a shell command wired to a lifecycle event in ~/.claude/settings.json. Claude Code passes JSON on stdin describing what's about to happen; the hook can exit with a non-zero code to block the action, or write to stdout to inject context.
{
"hooks": {
"PreToolUse": [{ "matcher": "Write", "hooks": [{ "type": "command", "command": "your-script.sh" }] }]
}
}
Lifecycle events
| Event | When it fires |
|---|---|
SessionStart | Once, when a session opens |
PreToolUse | Before any tool call — can block it |
PostToolUse | After a tool completes |
Stop | When Claude finishes a turn |
SubagentStop | When a spawned subagent finishes |
PreToolUse is the most useful. It lets you gate, log, or reshape a tool call before it touches anything.
Three patterns worth knowing
1. Auto-format on write
Run your formatter every time Claude writes a file. No more "oops, forgot to format" commits.
# PostToolUse — matcher: "Write"
# stdin contains { "tool": "Write", "input": { "file_path": "..." } }
FILE=$(jq -r '.input.file_path' < /dev/stdin)
case "$FILE" in
*.ts|*.tsx) npx prettier --write "$FILE" ;;
*.py) black "$FILE" ;;
esac
2. Gate risky tool calls
Block bash commands that match patterns you don't want Claude running unsupervised.
# PreToolUse — matcher: "Bash"
CMD=$(jq -r '.input.command' < /dev/stdin)
if echo "$CMD" | grep -qE 'rm -rf|git push --force|DROP TABLE'; then
echo "Blocked: $CMD" >&2
exit 1
fi
Exit 1 cancels the tool call. Claude sees the stderr as the reason.
3. Inject context at session start
Load a file into Claude's context every time a session opens — design rules, project conventions, a daily note.
# SessionStart
cat ~/.claude/rules/design-dna.md
Whatever you print to stdout on SessionStart gets injected into the session context automatically.
Hook vs. slash command vs. agent
Use a hook when the trigger is an event (tool fires, session opens) and you want it to happen without typing anything.
Use a slash command when you want to trigger something manually, on demand.
Use an agent when the logic is complex enough to need its own context — reading files, making decisions, calling other tools.
Hooks are cheap and automatic. Keep them simple: one job, exit fast.
Getting started
- Create a script in
~/.claude/hooks/. - Make it executable:
chmod +x ~/.claude/hooks/your-hook.sh. - Add the entry to the
hookskey in~/.claude/settings.json. - Test with a dry run before wiring it to a destructive event.
The official Claude Code docs cover the full settings schema and available matchers.