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:

  1. Event — when it fires (before a tool runs, after a tool runs, when the session starts, etc.)
  2. Matcher — which tool triggers it (optional — omit to match all tools for that event)
  3. 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

EventWhen It FiresCommon Use
SessionStartClaude Code session beginsSet up environment, check dependencies
PreToolUseBefore Claude runs a toolValidate, block dangerous operations
PostToolUseAfter Claude runs a toolFormat code, run tests, log actions
StopClaude finishes its responseFinal checks, notifications
TeammateIdleAgent team member finishesCoordinate multi-agent workflows
TaskCreatedA new task is createdLog or validate task creation
TaskCompletedA task is marked doneRun 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 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:

VariableDescription
CLAUDE_FILE_PATHPath of the file being read, written, or edited
CLAUDE_TOOL_INPUTThe full input passed to the tool
CLAUDE_TOOL_NAMEName of the tool being used
CLAUDE_SESSION_IDCurrent session identifier

Use these to write hooks that react to specific files, commands, or patterns.

Hooks vs. Rules vs. Instructions

MechanismEnforcementWhen
CLAUDE.md instructionsProbabilistic — LLM may ignoreEvery session
RulesProbabilistic — scoped by file globWhen matching files are touched
HooksDeterministic — always runsOn 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:

  1. Check the event name — it is case-sensitive (PostToolUse, not posttooluse)
  2. Check the matcher — it matches against the tool name, not the command
  3. Run /status to confirm your settings.json is loaded
  4. 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.