Introducing hooks
Hooks have a reputation for being the most confusing feature in Claude Code. They are not — they are just a feature with a lot of moving parts introduced all at once. This lesson establishes the mental model, and the next two walk through building one step by step.
A hook is a command that runs before or after Claude executes a tool. That is the entire concept. Everything else — matchers, exit codes, stdin JSON, stderr feedback — is detail on top of that one-line definition.
Where hooks fit in the tool execution flow
Remember the gather-plan-act loop from Module 1. When Claude decides to use a tool — reading a file, writing to disk, running a command — Claude Code performs the actual operation and feeds the result back. Hooks insert themselves on either side of that operation.
PreToolUse hooks run before the tool executes. They can inspect what Claude is about to do — the tool name, the file path, the arguments — and block the tool call if they do not like it. Exit with a specific code and the operation never happens.
PostToolUse hooks run after the tool executes. They see what happened — what file got written, what command got run — and can provide feedback to Claude via stderr. They cannot undo the operation, but they can tell Claude that something about it was wrong so Claude fixes it on the next turn.
The asymmetry is the key insight: PreToolUse can block, PostToolUse can correct. Two different levers for two different kinds of rules.
How you configure a hook
Hooks live in the Claude Code settings file. There are three levels — global, project, and personal — matching the CLAUDE.md pattern. You can edit settings directly or use the /hooks command, which is a guided UI for adding and managing them.
Every hook has two required pieces:
- A matcher that says which tools this hook applies to. For example,
Readtargets just the Read tool.Write|Edit|MultiEdittargets any tool that modifies files. The pipe is a regex OR. - A command that says what to run. It can be a shell command, a script, a CLI tool — anything your terminal would accept.
A minimal configuration might look like this conceptually:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Read", "command": "node ./hooks/check-read.js" }
],
"PostToolUse": [
{ "matcher": "Write|Edit|MultiEdit", "command": "npm run lint" }
]
}
}
You will see the real syntax in the next lesson — the point here is just the shape.
What hooks are actually useful for
Once the model clicks, the use cases fall out naturally.
Auto-formatting after edits. A PostToolUse hook on Write|Edit runs Prettier on anything Claude wrote.
Running tests after changes. A PostToolUse hook runs the test suite after edits to source files. If tests fail, feed the output back and let Claude fix it.
Blocking sensitive files. A PreToolUse hook on Read|Grep blocks access to .env, credentials files, or anything else Claude should not see. This is the scenario the next two lessons build out.
Type checking. A PostToolUse hook runs tsc --noEmit after TypeScript edits and feeds errors back so Claude fixes call sites it missed.
Code quality checks. Any linter, static analyser, or custom script can hang off a PostToolUse hook and participate in a self-correcting feedback loop.
The feedback loop is the payoff
The single most valuable thing hooks do is turn one-shot Claude interactions into self-correcting workflows. PostToolUse writes feedback to stderr; Claude reads it; Claude fixes whatever was wrong. Without you in the loop.
That is the pattern to hold on to while the next lesson walks you through the implementation details. Matchers, exit codes, stdin JSON — they are all in service of making that feedback loop work.
Key Takeaways
- 1A hook is simply a command that runs before or after Claude executes a tool.
- 2PreToolUse hooks run before the tool and can block the call; PostToolUse hooks run after and can feed results back to Claude but cannot block.
- 3Every hook has a matcher (which tools to target, like `Read` or `Write|Edit|MultiEdit`) and a command (what to run).
- 4Hooks unlock auto-formatting, test running, sensitive-file blocking, type checking, and any other quality check you want to automate.
- 5The real payoff is the self-correcting feedback loop: a PostToolUse hook writes a quality check's output to stderr, and Claude fixes the issue on the next turn.