Skip to main content
Hooks let you automate actions that happen before or after tool calls, at session boundaries, and at other lifecycle points. They are defined in settings.json under the hooks key and are available at the user, project, and managed settings levels.

Quick example

Run prettier after every file write:
.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $CLAUDE_FILE_PATHS"
          }
        ]
      }
    ]
  }
}

Interactive configuration

Run /hooks inside Claude Code to open the hooks configuration menu. The menu lists available events, lets you add or remove matchers and hook commands, and writes changes back to the appropriate settings file.

Hook structure

A hook configuration maps event names to arrays of matcher objects. Each matcher object specifies an optional tool filter and a list of hook commands to run when the filter matches.
{
  "hooks": {
    "<EventName>": [
      {
        "matcher": "<optional filter>",
        "hooks": [
          { "type": "command", "command": "..." },
          { "type": "prompt",  "prompt": "..." }
        ]
      }
    ]
  }
}

Matcher

The matcher field is optional. When present it uses permission rule syntax — the same patterns used in permissions.allow and permissions.deny — to filter which tool calls trigger the hook.
{ "matcher": "Bash(git *)" }     // Only git commands
{ "matcher": "Write(*.ts)" }     // Only writes to TypeScript files
{ "matcher": "Read" }            // Any Read call
When matcher is absent the hooks run for every tool call under that event.

Hook events

Fires immediately before Claude executes a tool. Use this event to validate, log, or block tool calls before they run. A non-zero exit code from a command hook can abort the tool call.
Fires after a tool completes successfully. Use this to run formatters, linters, or notifications based on what Claude just did.
Fires when a tool call fails. Useful for cleanup or alerting on errors.
Fires when Claude Code emits a notification (e.g. when it needs user input). Use to bridge notifications to desktop or webhook systems.
Fires when the user submits a prompt, before Claude processes it. Useful for logging, prompt augmentation, or pre-submission validation.
Fires once when a new session begins. Use for setup tasks, welcome scripts, or environment validation.
Fires when the session ends. Use for teardown, summaries, or syncing session data.
Fires when Claude stops (completes a turn). Useful for post-turn processing.
Fires when Claude stops due to an error.
Fire when a sub-agent (spawned by the main agent) starts or stops.
Fire before and after context compaction. Use PreCompact to capture the full context before it is compressed; use PostCompact to run post-compaction setup.
Fire when Claude requests a permission or when a permission is denied. Useful for audit logging.
Fires once at startup, before SessionStart. Runs even in bare mode. Suitable for environment initialization.
Fires after CLAUDE.md instructions have been loaded into the session context.
Fires when a file is modified (e.g. from a Write or Edit tool call). The changed path is available via $CLAUDE_FILE_PATHS.
Fires when the working directory changes.
Fire when a git worktree is created or removed.

Hook types

Command hook

Runs a shell command. This is the most common hook type.
{
  "type": "command",
  "command": "echo $CLAUDE_TOOL_NAME >> ~/.claude/tool-log.txt"
}
type
string
required
Must be "command".
command
string
required
Shell command to execute. Supports environment variable expansion.
if
string
Permission rule syntax filter applied before the hook runs (e.g. "Bash(git *)", "Write(*.ts)"). Avoids spawning the hook process when the tool call does not match.
shell
string
Shell interpreter: "bash" (uses your $SHELL, bash/zsh/sh) or "powershell" (uses pwsh). Defaults to "bash".
timeout
number
Timeout in seconds for this specific command. Overrides the global hook timeout.
statusMessage
string
Custom message displayed in the spinner while the hook runs.
once
boolean
When true, the hook runs once and is removed from the configuration after execution.
async
boolean
When true, the hook runs in the background without blocking Claude Code.
asyncRewake
boolean
When true, the hook runs in the background and wakes the model if it exits with code 2 (indicating a blocking error). Implies async: true.

Prompt hook

Evaluates an LLM prompt. Use this to delegate verification or enrichment to a model without spawning a sub-agent.
{
  "type": "prompt",
  "prompt": "Review the tool output and report any issues: $ARGUMENTS"
}
type
string
required
Must be "prompt".
prompt
string
required
The prompt to send to the model. Use $ARGUMENTS as a placeholder for the hook input JSON.
if
string
Permission rule syntax filter (same as command hook).
model
string
Model to use for this prompt hook (e.g. "claude-sonnet-4-6"). Defaults to the configured small fast model.
timeout
number
Timeout in seconds for this prompt evaluation.
statusMessage
string
Custom status message shown in the spinner.
once
boolean
When true, the hook runs once and is removed after execution.

Agent hook

Spawns a full sub-agent to verify or react to a tool call. More powerful than a prompt hook — the agent can itself call tools.
{
  "type": "agent",
  "prompt": "Verify that unit tests ran and passed. Use $ARGUMENTS for input context.",
  "timeout": 120
}
type
string
required
Must be "agent".
prompt
string
required
Prompt describing what to verify or do. Use $ARGUMENTS as a placeholder for the hook input JSON.
if
string
Permission rule syntax filter.
model
string
Model to use for the agent. Defaults to Haiku.
timeout
number
Timeout in seconds for agent execution. Default is 60.
statusMessage
string
Custom status message shown in the spinner.
once
boolean
When true, the hook runs once and is removed after execution.

HTTP hook

POSTs the hook input JSON to a URL. Useful for webhooks, audit logging services, and external integrations.
{
  "type": "http",
  "url": "https://hooks.example.com/claude-events",
  "headers": {
    "Authorization": "Bearer $WEBHOOK_TOKEN"
  },
  "allowedEnvVars": ["WEBHOOK_TOKEN"]
}
type
string
required
Must be "http".
url
string
required
URL to POST the hook input JSON to. Must be a valid URL.
if
string
Permission rule syntax filter.
timeout
number
Timeout in seconds for the HTTP request.
headers
object
Additional headers to include in the request. Values may reference environment variables using $VAR_NAME or ${VAR_NAME} syntax. Only variables listed in allowedEnvVars will be interpolated.
allowedEnvVars
array
Explicit list of environment variable names that may be interpolated in header values. Variables not listed here are left as empty strings. Required for env var interpolation to work.
statusMessage
string
Custom status message shown in the spinner.
once
boolean
When true, the hook runs once and is removed after execution.

Common patterns

Format on write

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $CLAUDE_FILE_PATHS",
            "statusMessage": "Formatting..."
          }
        ]
      }
    ]
  }
}

Run tests after edits

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(src/**)",
        "hooks": [
          {
            "type": "command",
            "command": "npm test --passWithNoTests",
            "async": true
          }
        ]
      }
    ]
  }
}

Audit log every tool call

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://audit.example.com/log",
            "headers": {
              "X-API-Key": "$AUDIT_KEY"
            },
            "allowedEnvVars": ["AUDIT_KEY"],
            "async": true
          }
        ]
      }
    ]
  }
}

Verify git commit is clean before stopping

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Check that all staged changes have been committed and the working tree is clean. Report any uncommitted files.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Enterprise controls

Set in any settings file to completely disable hook and statusLine execution.
{ "disableAllHooks": true }
Set in managed settings to prevent user/project/local hooks from running. Only hooks defined in managed-settings.json will execute.
Restrict which URLs HTTP hooks may POST to. Supports * as a wildcard. Arrays merge across settings sources.
{ "allowedHttpHookUrls": ["https://hooks.example.com/*"] }
Restrict which environment variable names HTTP hooks may interpolate into headers. Arrays merge across settings sources.

Build docs developers (and LLMs) love