Claude Code Hooks: A Practical Guide with Examples
By Tyler Cyert
Claude Code hooks are shell commands that run automatically at specific points in Claude Code's lifecycle. They give you deterministic control over agent behavior — instead of hoping the LLM remembers to format your code or run tests, you make it happen every time, no exceptions.
Hooks solve a fundamental problem with AI coding agents: LLMs are probabilistic. They usually follow instructions, but "usually" is not good enough for formatting, security checks, or deployment gates. Hooks are the deterministic layer on top of the probabilistic one.
How Hooks Work
A hook has three parts:
- Event — when it fires (before a tool runs, after a tool runs, when the session starts, etc.)
- Matcher — which tool triggers it (optional — omit to match all tools for that event)
- Command — the shell command to execute
Hooks live in your settings.json under the hooks key, not in separate files. Each hook entry specifies an event name, an optional matcher, the command to run, and a description.
Lifecycle Events
| Event | When It Fires | Common Use |
|---|---|---|
SessionStart | Claude Code session begins | Set up environment, check dependencies |
PreToolUse | Before Claude runs a tool | Validate, block dangerous operations |
PostToolUse | After Claude runs a tool | Format code, run tests, log actions |
Stop | Claude finishes its response | Final checks, notifications |
TeammateIdle | Agent team member finishes | Coordinate multi-agent workflows |
TaskCreated | A new task is created | Log or validate task creation |
TaskCompleted | A task is marked done | Run verification, notify |
PreToolUse and PostToolUse are the ones you will use most. PreToolUse is your gate — block dangerous actions before they happen. PostToolUse is your enforcer — clean up after every change.
Exit Codes
Your hook's exit code controls what happens next:
- Exit 0 — Proceed normally
- Exit 2 — Block the action (PreToolUse only). Claude sees the block and adjusts.
- Any other exit — Logged as an error, action proceeds
Exit code 2 is powerful. A PreToolUse hook that exits 2 stops Claude from running the matched tool entirely. Use this for security gates, forbidden directories, or compliance checks.
Five Production Hooks
1. Auto-Format on Every Edit
The most common hook. A PostToolUse hook matching Edit|Write that runs prettier --write $CLAUDE_FILE_PATH. Never argue about formatting again.
2. Block Dangerous Commands
A PreToolUse hook matching Bash that pipes $CLAUDE_TOOL_INPUT through grep looking for patterns like rm -rf, drop table, or git push --force. If found, exit 2 blocks the command. Otherwise, exit 0 allows it.
3. Run Tests After Code Changes
A PostToolUse hook matching Edit|Write that runs npm run test --silent and shows the last few lines of output. Catch regressions immediately after every code change.
4. Protect Sensitive Directories
A PreToolUse hook matching Edit|Write that checks $CLAUDE_FILE_PATH for protected paths like /migrations/. If the path matches, exit 2 blocks the write. Use this to prevent accidental modification of migration files, lock files, or generated code.
5. Lint Check on Session Start
A SessionStart hook that runs npm run lint --silent and reports any issues. Ensure the codebase is clean before Claude starts working on it.
Environment Variables in Hooks
Claude Code injects context into your hook's environment:
| Variable | Description |
|---|---|
CLAUDE_FILE_PATH | Path of the file being read, written, or edited |
CLAUDE_TOOL_INPUT | The full input passed to the tool |
CLAUDE_TOOL_NAME | Name of the tool being used |
CLAUDE_SESSION_ID | Current session identifier |
Use these to write hooks that react to specific files, commands, or patterns.
Hooks vs. Rules vs. Instructions
| Mechanism | Enforcement | When |
|---|---|---|
| CLAUDE.md instructions | Probabilistic — LLM may ignore | Every session |
| Rules | Probabilistic — scoped by file glob | When matching files are touched |
| Hooks | Deterministic — always runs | On lifecycle event |
Use CLAUDE.md for guidelines. Use rules for file-specific conventions. Use hooks for things that must happen every time, without exception.
Combining Hooks with Other Features
Hooks work best as part of a layered system. Your CLAUDE.md sets the project context. Permissions control which tools Claude can access. Rules add file-specific conventions. Hooks enforce the non-negotiable behaviors — formatting, testing, security gates — that no amount of instruction rewriting should override.
Debugging Hooks
If a hook is not firing:
- Check the event name — it is case-sensitive (
PostToolUse, notposttooluse) - Check the matcher — it matches against the tool name, not the command
- Run
/statusto confirm your settings.json is loaded - Test the command manually in your terminal first
Setting Up Hooks with DotBox
Configuring hooks means writing JSON with exact event names, matchers, and shell commands. DotBox lets you select lifecycle events from a dropdown, configure matchers visually, and generates the correct settings.json structure — part of your complete .claude/ directory export.