Claude Code Hooks: Automate Your Workflow with Event-Driven Scripts

Updated February 2026 · 10 min read

Hooks are one of Claude Code's most powerful features. They let you run shell commands automatically in response to events during a Claude Code session -- before or after tool calls, when Claude stops responding, or when notifications fire. Think of them as git hooks, but for your AI coding assistant.

Unlike rules or skills that shape how Claude thinks, hooks shape what happens around Claude's actions. They're deterministic, run every time their trigger fires, and execute outside the model -- meaning they're fast, reliable, and not subject to the model deciding whether to follow an instruction.

How Hooks Work

Hooks are configured in your Claude Code settings file (.claude/settings.json at the project or user level). Each hook specifies a matcher (which event to respond to), a command to run, and optionally whether it should block execution or run in the background.

When an event fires, Claude Code checks if any hooks match, executes the command, and feeds the result (stdout) back as context. If a hook exits with a non-zero code, it can block the action from proceeding -- giving you a veto over Claude's behavior.

Hook Types

PreToolUse

Runs before Claude executes a tool. This is your chance to validate, gate, or modify what Claude is about to do. If the hook exits non-zero, the tool call is blocked and Claude sees the error output.

Common tool names you can match against:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": "python3 .claude/validate-command.py"
      }
    ]
  }
}

The hook receives the tool input as JSON on stdin. For a Bash tool call, that includes the command string, so your validation script can parse it and decide whether to allow it.

PostToolUse

Runs after a tool completes. Useful for triggering side effects like auto-formatting code after Claude writes a file, running linters, or logging what happened.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hook": "npx prettier --write $CLAUDE_FILE_PATH"
      }
    ]
  }
}

PostToolUse hooks receive both the tool input and the tool output on stdin. The exit code of a PostToolUse hook doesn't block anything -- the action already happened -- but the stdout is still fed back to Claude as context.

Notification

Fires when Claude Code sends a notification (for example, when a long-running task completes and Claude wants to alert you). You can wire this up to system notifications, Slack webhooks, or custom alerting.

{
  "hooks": {
    "Notification": [
      {
        "hook": "terminal-notifier -title 'Claude Code' -message \"$CLAUDE_NOTIFICATION\""
      }
    ]
  }
}

Stop

Runs when Claude finishes its turn (stops generating). This is useful for post-processing like running the full test suite, generating summaries, or updating dashboards.

{
  "hooks": {
    "Stop": [
      {
        "hook": "npm test 2>&1 | tail -20"
      }
    ]
  }
}

If a Stop hook fails (non-zero exit), Claude sees the output and continues working -- essentially getting another turn to fix whatever the hook flagged. This creates a powerful feedback loop: Claude writes code, tests run, failures feed back to Claude, and it iterates.

Practical Examples

Auto-format on every file write

Never worry about Claude writing code that doesn't match your style guide:

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

Prevent writes to protected files

Block Claude from modifying your lock files, CI config, or other sensitive paths:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hook": "python3 -c \"import sys,json; d=json.load(sys.stdin); p=d.get('file_path',''); sys.exit(1 if any(x in p for x in ['package-lock.json','.github/','migration']) else 0)\""
      }
    ]
  }
}

Auto-run tests after code changes

Use a Stop hook to run tests every time Claude finishes a turn, creating a continuous feedback loop:

{
  "hooks": {
    "Stop": [
      {
        "hook": "npm test -- --bail 2>&1 | tail -30"
      }
    ]
  }
}

Environment Variables

Hooks have access to several environment variables set by Claude Code:

Hook commands also receive structured JSON on stdin containing the full tool input (and output, for PostToolUse hooks), letting you write sophisticated validation logic.

Tips for Writing Hooks

Hooks vs Skills vs CLAUDE.md

It's worth understanding where hooks fit in the Claude Code customization stack:

Use CLAUDE.md to tell Claude how to work. Use skills to define what Claude can do. Use hooks to enforce what must happen around Claude's actions. For a deeper comparison, see our guide on Skills vs MCP.