Claude Code Hooks Cheat Sheet
This page condenses Claude Code hooks into one page: events, matchers, exit codes, and Node scripts that block commands, format files, and notify you. You can also download the information as a printable cheat sheet:
Free Bonus: Claude Code Hooks Cheat Sheet
Get the Claude Code Hooks Cheat Sheet (PDF), a printable reference to hook anatomy, lifecycle events, reading the event payload, exit codes, auto-formatting, notifications, and JSON replies:
Want a guided path instead of a reference? Start learning at realjs.com.
Hook Anatomy
- A hook is a command that runs on an event, filtered by a matcher
- It fires every time, no matter what the model decides
- Omit
matcheror use"*"to match every tool "Bash"matches one tool,"Write|Edit"several,"^Notebook"is a regex- Hooks merge across files; commit
.claude/settings.jsonto share them
| Settings file | Scope |
|---|---|
~/.claude/settings.json |
All your projects |
.claude/settings.json |
This project, shareable |
.claude/settings.local.json |
This project, gitignored |
Register a Hook
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node hooks/guard.mjs"
}
]
}
]
}
}
Lifecycle Events
- Exit
2can blockPreToolUse,UserPromptSubmit,Stop, andPreCompact PostToolUsecan’t block; the tool already ran- Tool hooks also fire inside subagents (
agent_idis set)
| Event | Fires |
|---|---|
SessionStart |
A session begins or resumes |
UserPromptSubmit |
You submit a prompt |
PreToolUse |
Before a tool call runs |
PostToolUse |
After a tool call succeeds |
Notification |
Claude Code needs your attention |
Stop |
Claude finishes a response |
PreCompact |
Before context compaction |
SessionEnd |
The session terminates |
Read the Event
- The event arrives as JSON on standard input
- Always present:
hook_event_name,session_id,cwd - Tool events add
tool_nameandtool_input
Parse the Payload
import { readFileSync } from "node:fs";
const raw = readFileSync(0, "utf8");
const event = JSON.parse(raw);
event.hook_event_name // 'PreToolUse'
event.tool_name // 'Bash'
event.tool_input // { command: 'yarn add' }
Exit-Code Contract
exit 1does not block. Only2does- Steer, don’t just deny: tell Claude what to run instead
- Stderr on exit
0goes to the debug log only; Claude never sees it - A hook that hits its timeout (600 s default) is discarded; the action proceeds
| Exit code | Effect |
|---|---|
0 |
Proceed; stdout may carry JSON |
2 |
Block; stderr goes to Claude as feedback |
| Other | Non-blocking error; the action proceeds |
Block yarn, Suggest npm
import { readFileSync } from "node:fs";
const raw = readFileSync(0, "utf8");
const event = JSON.parse(raw);
const cmd = event.tool_input?.command ?? "";
if (/^yarn( |$)/.test(cmd)) {
console.error("Use npm instead of yarn.");
process.exit(2); // Blocked
}
process.exit(0); // Allowed
Test the Guard Without Claude
$ echo '{"tool_input":{"command":"yarn"}}' \
| node hooks/guard.mjs; echo "exit $?"
Use npm instead of yarn.
exit 2
Free Bonus: Download the Claude Code Hooks Cheat Sheet PDF and keep the essentials at hand.
Auto-Format Every Edit
- Match
"Write|Edit"onPostToolUse - Hooks inherit your shell
PATH, notnode_modules/.bin, so call tools withnpx - Use
spawnSync, which returns errors instead of throwing PostToolUsealso seestool_response, the tool’s result- Files rewritten by a
Bashcommand don’t triggerWrite|Edithooks
Install the Tools Locally
$ npm install --save-dev prettier
Format the Changed File
import { existsSync, readFileSync }
from "node:fs";
import cp from "node:child_process";
const raw = readFileSync(0, "utf8");
const event = JSON.parse(raw);
const path = event.tool_input?.file_path;
if (path?.endsWith(".js")
&& existsSync(path)) {
cp.spawnSync("npx", [
"prettier", "--write", path,
]);
}
Notify When Claude Stops
- Register under
"Stop", which fires after every response, not just the last
Desktop Notification
import { readFileSync } from "node:fs";
import cp from "node:child_process";
import { platform } from "node:os";
readFileSync(0); // Consume the event
const msg = "Claude finished responding";
const note = `display notification "${msg}"`;
const [bin, ...args] =
platform() === "darwin"
? ["osascript", "-e", note]
: ["notify-send", "Claude Code", msg];
if (cp.spawnSync(bin, args).error) {
console.log(msg); // No notifier
}
Structured JSON Replies
- Exit
0and print one JSON object for finer control StopandPostToolUse:{"decision": "block", "reason": "..."}
Allow, Deny, or Ask on PreToolUse
const reply = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask", // allow, deny
permissionDecisionReason: "Edits prod",
},
};
console.log(JSON.stringify(reply));
Troubleshooting
- Not firing? Check the event name, the matcher, and the JSON commas
/hookslists what actually loaded and from which file
Trace a Hook in the Debug Log
$ claude --debug-file /tmp/hooks-debug.txt
$ grep -A2 "Hook" /tmp/hooks-debug.txt
[DEBUG] Hook PreToolUse:Bash error:
Use npm instead of yarn.
[DEBUG] Hook denied tool use for Bash
Want to go deeper on Claude Code hooks?
RealJS teaches the mental models that let you write, read, and review modern JavaScript with confidence, whether you or an AI wrote it.
Learn more at realjs.com 💡
You can download this information as a printable cheat sheet:
Free Bonus: Claude Code Hooks Cheat Sheet
Get the Claude Code Hooks Cheat Sheet (PDF), a printable reference to hook anatomy, lifecycle events, reading the event payload, exit codes, auto-formatting, notifications, and JSON replies: