quiccsite

Claude Code Hooks: A Complete Guide with Examples

Hooks let you run shell commands automatically when Claude Code does things — before or after tool calls, on session start, on notifications. They're the escape hatch for enforcing rules that CLAUDE.md alone can't guarantee.

What are hooks?

Hooks are shell commands defined in your Claude Code settings that fire on specific events. They run synchronously — Claude waits for them to finish before continuing.

If a hook exits with a non-zero code, the action is blocked and Claude sees the error output.

Where to define them

Hooks live in your settings file. You can scope them globally or per-project:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "npm run lint --fix $CLAUDE_FILE_PATH"
      }
    ]
  }
}

Available hook events

PreToolUse

Fires before Claude executes a tool. Return a non-zero exit code to block it.

Use cases:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "echo $CLAUDE_TOOL_INPUT | grep -q 'rm -rf' && echo 'Blocked: rm -rf' && exit 1 || exit 0"
      }
    ]
  }
}

PostToolUse

Fires after a tool completes successfully. This is the most commonly used hook.

Use cases:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "prettier --write $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
      }
    ]
  }
}

Notification

Fires when Claude sends a notification (usually when it's done with a long task and waiting for input).

{
  "hooks": {
    "Notification": [
      {
        "command": "osascript -e 'display notification \"Claude is waiting\" with title \"Claude Code\"'"
      }
    ]
  }
}

SessionStart

Fires when a new Claude Code session begins.

{
  "hooks": {
    "SessionStart": [
      {
        "command": "echo 'Session started at $(date)' >> ~/.claude/session.log"
      }
    ]
  }
}

Environment variables available in hooks

When your hook runs, Claude Code sets these environment variables:

Variable Description
CLAUDE_FILE_PATH The file being read/written/edited
CLAUDE_TOOL_INPUT JSON string of the tool's input parameters
CLAUDE_TOOL_NAME Name of the tool (Write, Edit, Bash, etc.)

The matcher field

The matcher field filters which tools trigger the hook. It's a regex pattern matched against the tool name.

Practical examples

Auto-format on save

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
      }
    ]
  }
}

The ; exit 0 ensures the hook never fails — you don't want formatting errors to block Claude.

Prevent editing lockfiles

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "echo $CLAUDE_FILE_PATH | grep -qE '(package-lock|yarn\\.lock|pnpm-lock)' && echo 'Do not edit lockfiles directly. Use npm/yarn/pnpm install instead.' && exit 1 || exit 0"
      }
    ]
  }
}

Run tests after code changes

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "echo $CLAUDE_FILE_PATH | grep -qE '\\.(ts|tsx)$' && npm test -- --related $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
      }
    ]
  }
}

Desktop notification when Claude finishes

On macOS:

{
  "hooks": {
    "Notification": [
      {
        "command": "osascript -e 'display notification \"Claude needs your attention\" with title \"Claude Code\" sound name \"Glass\"'"
      }
    ]
  }
}

Tips

Always exit 0 in PostToolUse hooks unless you genuinely want to signal failure. A formatting error shouldn't stop Claude from working.

Keep hooks fast. They run synchronously. A slow hook means Claude waits on every single tool call, eating into your token budget as context accumulates. If you need to run something expensive, background it with &.

Test hooks manually first. Run the command yourself with sample values before putting it in settings. A broken hook will confuse both you and Claude.

Use PreToolUse sparingly. Blocking hooks that are too aggressive will make Claude feel broken — it'll keep trying and failing. Reserve them for genuinely dangerous operations.

Hooks are not CLAUDE.md. Use CLAUDE.md for conventions and guidelines. Use hooks for hard enforcement — things that should never happen regardless of what Claude decides. For reusable workflows that don't need enforcement, consider skills instead.