nikolas.sapa
April 16, 2026

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

EventWhen it fires
SessionStartOnce, when a session opens
PreToolUseBefore any tool call — can block it
PostToolUseAfter a tool completes
StopWhen Claude finishes a turn
SubagentStopWhen 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

  1. Create a script in ~/.claude/hooks/.
  2. Make it executable: chmod +x ~/.claude/hooks/your-hook.sh.
  3. Add the entry to the hooks key in ~/.claude/settings.json.
  4. 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.