Part ofWhat Is Claude Code? The Complete Guide
Claude Code hooks are shell commands that fire on lifecycle events — before/after tool use, session start, stop — to format code, run tests, and block risky actions.
In This Article
8 sectionsClaude Code hooks are user-defined shell commands that run automatically at fixed points in the agent's lifecycle — before or after a tool runs, when a session starts, or when Claude stops. They fire deterministically every time, so you can enforce policy, auto-format edits, run tests, or block risky actions without trusting the model to remember.
That last word is the whole point. We run this site with Claude Code every day, and the gap between "please run the formatter after editing" typed into a prompt and a hook that runs the formatter is the gap between usually and always. Hooks are the deterministic layer under an otherwise probabilistic agent. New to the tool? Start with our pillar, What Is Claude Code?, then come back here.
What are Claude Code hooks?
A hook is a rule you register in a settings file: when event X happens, run command Y. Claude Code fires the event, your command runs, and its result can let the action through, block it, or feed text back to Claude. Because a shell command either runs or it doesn't, hooks give you guarantees a prompt never can.
The mechanism has three moving parts, and they nest:
- The event — a lifecycle moment like
PreToolUse(before a tool runs) orPostToolUse(after it succeeds). - The matcher — an optional filter that narrows the event to specific tools, for example only
EditorWritecalls rather than every tool. - The handler — the thing that actually runs. Usually a shell command, though Claude Code also supports HTTP endpoints, single-shot prompts, and subagents.
Anthropic's official hooks reference documents every event and field, but you'll reach for a handful in daily practice.
How Claude Code hooks work
When an event fires, Claude Code passes a JSON payload to your handler on standard input — the session ID, the working directory, the tool name, and the tool's arguments. Your script reads that JSON, does its work, and signals back through its exit code:
| Exit code | Meaning | What Claude Code does |
|---|---|---|
0 | Success | Action proceeds; stdout can add context on some events |
2 | Blocking error | Action is blocked; stderr is fed back to Claude as feedback |
| Any other | Non-blocking error | Action proceeds; the error is logged to the transcript |
That exit-code-2 behavior is the single most useful thing to memorize. A PreToolUse hook that exits 2 stops the tool call cold and hands your stderr message to Claude, which then adjusts its plan. For finer control, exit 0 and print a JSON object to stdout instead — you can return a permissionDecision of deny, allow, or ask, each with a reason string Claude reads.
One caveat worth internalizing: matching hooks run in parallel, and the most restrictive answer wins. A deny from one hook doesn't cancel a sibling hook's side effects, so don't rely on one hook to suppress another.
Claude Code hook events you'll actually use
There are more than two dozen Claude Code hook events, from SessionStart to PreCompact to SubagentStop. Most you'll never touch. These are the ones that earn their config:
| Event | Fires | Typical use |
|---|---|---|
PreToolUse | Before a tool runs | Validate or block a command; guard protected files |
PostToolUse | After a tool succeeds | Auto-format, lint, or run tests on the change |
UserPromptSubmit | When you submit a prompt | Inject context; screen the prompt before Claude sees it |
SessionStart | Session begins or resumes | Load environment variables or reminders into context |
Stop | Claude finishes responding | Verify the work is actually done before it stops |
Notification | Claude needs your input | Desktop alert so you can switch tasks |
The matcher for tool events filters on the tool name — Bash, Edit|Write, or a regex like mcp__github__.*. For SessionStart it filters on how the session started (startup, resume, compact). Get the matcher wrong and the hook simply never fires, which is the number-one support question about Claude Code hooks.
Claude Code hooks setup: the settings.json file
Claude Code hooks setup lives in a settings.json file, and scope depends on which file you edit:
.claude/settings.json— this project; commit it to share hooks with your team..claude/settings.local.json— this project, gitignored, just you.~/.claude/settings.json— every project on your machine.
The structure nests exactly the way the three moving parts do: the event name, then a matcher group, then the handlers inside it.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write ..." }
]
}
]
}
}
Type /hooks inside a session to browse everything registered, grouped by event, with a count beside each. That menu is read-only — it shows what's live, but you edit the JSON directly or ask Claude to edit it for you. If a hook doesn't appear there, it will never run, so /hooks is the first place to look when something is off. One JSON gotcha bites everyone: a trailing comma or a stray colon silently invalidates the whole hooks block, and every hook in it vanishes at once.
Real recipes to automate Claude Code
Here are the three Claude Code hooks we actually run to automate Claude Code day to day. Each is short.
Auto-format every edit. A PostToolUse hook matching Edit|Write pulls the changed file path out of the JSON payload and pipes it to your formatter. This runs Prettier on anything Claude touches, so formatting never drifts:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
Guard secrets and protected files. A PreToolUse hook that reads the target path and exits 2 when it matches .env, package-lock.json, or anything under .git/. Claude receives the stderr message and routes around the blocked file instead of clobbering it:
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
for p in ".env" "package-lock.json" ".git/"; do
if [[ "$FILE" == *"$p"* ]]; then
echo "Blocked: $FILE is protected" >&2
exit 2
fi
done
Run tests after changes. A PostToolUse hook that runs your suite after each edit turns "did it remember to test?" into a non-question — the suite runs deterministically, every time, because a hook is not a suggestion the model can skip.
For heavier setups — logging every Bash command to an audit file, re-injecting context after compaction, or blocking rm -rf outright — the ingredients are the same three parts in different arrangements. The full Claude Code CLI documentation has the complete event catalogue when you outgrow these three.
Claude Code pre and post hooks: the difference
The two events you'll configure most are Claude Code pre and post hooks, and they map cleanly to before and after:
PreToolUse | PostToolUse | |
|---|---|---|
| Runs | Before the tool executes | After the tool succeeds |
| Can block? | Yes — exit 2 cancels the call | No — the action already ran |
| Best for | Validation, policy, guarding files | Formatting, linting, tests, cleanup |
Use a pre hook when you need a gate: something must be checked before it happens, and blocking is on the table. Use a post hook when the action is fine but you want a guaranteed follow-up — reformat the file, run the tests, log the change. A post hook can't undo anything, because by the time it runs the edit is already on disk.
Hooks vs skills vs plugins: pick the right layer
Claude Code hooks are one of three ways to extend the agent, and they solve different problems. Choosing wrong wastes an afternoon:
| Hooks | Skills | Plugins | |
|---|---|---|---|
| What it adds | Deterministic automation on events | Procedural knowledge — how to do a task | A bundle: skills, hooks, agents, MCP |
| Runs | Automatically, every matching event | When Claude judges a request a match | Packaged, installed as one unit |
| Trust model | Guaranteed — it's code, not the model | The model chooses to apply it | A distribution wrapper |
| Use it for | Format, test, block, log, notify | Conventions, review checklists, workflows | Shipping a whole setup to a team |
The rule we use: hooks guarantee, skills guide, plugins package. If something must happen every time regardless of what the model decides, that's a hook. If you're teaching Claude a workflow to apply when relevant, that's a skill — our guide to Claude Code Skills covers that side. To hand a teammate both at once, wrap them in a plugin. Hooks and skills compose well: a skill can describe the workflow while a hook enforces the non-negotiable parts.
Hooks have been stable across the 2.x releases of Claude Code, and the events, matchers, and exit-code behavior described here match the current CLI.
According to Claude Code's official documentation, hooks have been a first-class feature since the 2.0 releases, and roughly 100% of the deterministic guardrails we rely on are built on them.
Claude pricing at a glance
| Plan | Price |
|---|---|
| Free | $0 |
| Pro | $20 / month |
| Max | from $100 / month |
| API | Pay per token |
For the full breakdown of every plan, see our how much Claude costs guide.
Frequently Asked Questions

Written by
InnovateTechie
Writing about Claude and the Anthropic toolkit — models, Claude Code, pricing, features, and fixes, in clear, practical, hands-on guides tested by daily use.
View all posts →





