What you’ll know by the end of this check
- Why hooks are the only mechanism in Claude Code that’s deterministic
- The five hook events and what each one is good for
- How to block dangerous operations with a PreToolUse hook
The shortest possible answer
Hooks always run. That’s the whole pitch.
You can tell Claude in CLAUDE.md to format your code after every edit. Most of the time it will. Sometimes it won’t. A hook makes it happen every single time, no exceptions.
Hooks are shell commands Claude Code executes at specific points in its lifecycle. They’re configured in settings.json (project or user scope) and fire regardless of what the model decides.
The five events
| Event | Fires | Good for |
|---|---|---|
| PreToolUse | Before any tool call | Blocking dangerous operations, guardrails |
| PostToolUse | After a tool call completes | Auto-formatting, logging, audit trails |
| UserPromptSubmit | When you hit send, before Claude sees it | Preprocessing, prompt injection, guards |
| Stop | When Claude finishes responding | Notifications, cleanup, session summaries |
| Notification | When Claude sends a notification | Routing to Slack, desktop alerts |
Matchers let you scope a hook to specific tools — e.g., "Edit\|MultiEdit\|Write" fires only on file-modification tools.
The killer pattern: auto-format on edit
A PostToolUse hook with matcher "Edit|MultiEdit|Write" that runs Prettier (or gofmt, or black, or whatever your project uses) after every file change. Configure once, and formatting is solved forever.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{ "type": "command", "command": "sh .claude/hooks/format.sh \"$FILE_PATH\"" }
]
}
]
}
}
The script inside checks the file extension and calls the right formatter. Claude doesn’t have to remember. It’s just done.
Blocking with PreToolUse
PreToolUse hooks can block a tool call before it runs. Your hook receives the tool name and input as JSON on stdin. The exit code is the signal:
| Exit code | Meaning |
|---|---|
| 0 | Proceed normally |
| 2 | Block. stderr message gets fed back to Claude as feedback so it can adjust. |
| Anything else | Non-blocking error — shown to you but doesn’t stop the call |
This is how you enforce hard rules Claude can’t talk its way out of:
- Block writes to
src/schema/orconfig/production/ - Block
rm -rfin Bash commands - Block commits or pushes to
main - Block any operation touching a prod database connection string
Sharing hooks with your team
Hooks in .claude/settings.json are project-level and check into the repo. Every teammate gets the same hooks automatically. Use the CLAUDE_PROJECT_DIR environment variable in your commands so scripts reference the right paths regardless of where Claude is running.
The rule that captures the whole lesson
If something needs to happen every time without fail, don’t put it in a prompt. Put it in a hook.
Prompts are suggestions. CLAUDE.md is a suggestion. Hooks are law.
Things to try right now (15 minutes)
- Pick one thing you always want to happen after a file edit (format, lint, git add, whatever).
- Create
.claude/settings.jsonin your repo if it doesn’t exist. - Add a PostToolUse hook with a matcher and command.
- Edit a file with Claude. Confirm the hook fires.
- Commit
.claude/settings.jsonso the team benefits too.
The canonical version
Full official lesson is at anthropic.skilljar.com/claude-code-101/469798.
Ready to verify this check?
You have at least one hook configured in a real project. You can name the five events. You know the exit-code semantics for PreToolUse blocking. Mark it cleared — you’ve finished Claude Code 101.