Hooks System
Hooks run shell commands at specific lifecycle points — for automation, enforcement, and extension.
Hook Structure
Section titled “Hook Structure”{ "hooks": [ { "event": "PostToolUse", "if": { "tool": "Edit" }, "run": "sh", "args": ["-c", "npm run format -- $file_path"], "continueOnError": false } ]}Hook Events
Section titled “Hook Events”Session Events
Section titled “Session Events”| Event | When it fires |
|---|---|
SessionStart | When a session begins |
Setup | After initial setup |
InstructionsLoaded | After CLAUDE.md loads |
SessionEnd | When the session ends |
Tool Events
Section titled “Tool Events”| Event | When it fires |
|---|---|
PreToolUse | Before Claude runs a tool — can block/allow |
PostToolUse | After a tool succeeds |
PostToolUseFailure | After a tool fails |
PostToolBatch | After a batch of tools completes |
File Events
Section titled “File Events”| Event | When it fires |
|---|---|
FileChanged | When a file is modified |
CwdChanged | When the working directory changes |
Agent Events
Section titled “Agent Events”| Event | When it fires |
|---|---|
Notification | When Claude emits a notification |
Stop | Before Claude stops working |
SubagentStart | When a subagent spawns |
SubagentStop | When a subagent completes |
Exit Code Behavior
Section titled “Exit Code Behavior”| Exit code | Meaning |
|---|---|
0 | Success — continue |
1 | Error — stop Claude |
2 | Deny this specific tool call |
Matcher Syntax (if field)
Section titled “Matcher Syntax (if field)”{ "if": { "tool": "Edit", // match tool name "command": "npm test", // Bash: match command text "path": "src/**/*.ts", // match file path "param:isolation": "worktree" // match by parameter value }}Common Hook Examples
Section titled “Common Hook Examples”Auto-format after every edit
Section titled “Auto-format after every edit”{ "hooks": [{ "event": "PostToolUse", "if": { "tool": "Edit" }, "run": "sh", "args": ["-c", "prettier --write $file_path 2>/dev/null || true"], "continueOnError": true }]}Run lint after TypeScript edits
Section titled “Run lint after TypeScript edits”{ "hooks": [{ "event": "PostToolUse", "if": { "tool": "Edit", "path": "**/*.ts" }, "run": "sh", "args": ["-c", "npx tsc --noEmit 2>&1 | head -20"], "continueOnError": true }]}Block edits to protected files (exit 2 = deny)
Section titled “Block edits to protected files (exit 2 = deny)”{ "hooks": [{ "event": "PreToolUse", "if": { "tool": "Edit" }, "run": "bash", "args": ["-c", "if [[ $file_path =~ 'secrets' ]]; then exit 2; fi"] }]}Desktop notification when Claude stops
Section titled “Desktop notification when Claude stops”{ "hooks": [{ "event": "Stop", "run": "sh", "args": ["-c", "osascript -e 'display notification \"Claude stopped\" with title \"Claude Code\"'"] }]}Reload .env when it changes
Section titled “Reload .env when it changes”{ "hooks": [{ "event": "FileChanged", "if": { "path": ".env" }, "run": "sh", "args": ["-c", "set -a; source .env; set +a"] }]}Hook Input / Environment Variables
Section titled “Hook Input / Environment Variables”Hooks receive context as environment variables:
$file_path— path of the file being operated on$tool— name of the tool$command— bash command (for Bash tool)$message— notification message (for Notification event)
They also receive JSON via stdin with the full event payload.
View Hooks
Section titled “View Hooks”/hooks # in-session view of configured hooks🧠 Quick Recall
Section titled “🧠 Quick Recall”- Trick: “Hooks are law; prompts are advice.” (The harness runs them — the model can’t skip them.)
- Exit code 2 = block the action + explain why.
👨🏫 Teach It
Section titled “👨🏫 Teach It”- Show: a PostToolUse format-on-edit hook firing after a sloppy edit.
- They’ll trip on: expecting AI judgment in a hook — it’s your script, deterministic.
Learning path: ⬅ 22-skills-real-examples.md · Index · ➡ 24-hooks-real-examples.md
Written by Fenil Patel