Skip to main content
Hooks let you run custom logic before or after Claude Code invokes a tool. You can use them to log every tool call, block dangerous operations, auto-approve safe patterns, post results to a webhook, or run an LLM sub-agent to verify intent.

What hooks are

A hook is a shell command, HTTP request, LLM prompt, or agentic verifier attached to a hook event (such as PreToolUse) and optionally filtered by a matcher (such as "Bash"). When the event fires, Claude Code runs all matching hooks in the order they appear in your settings. Hooks are configured under the hooks key in any settings.json file — user, project, or local. See Settings for file locations.

Hook events

The following hook events are available:
EventWhen it fires
PreToolUseBefore a tool call is sent to Claude
PostToolUseAfter a tool call completes successfully
PostToolUseFailureAfter a tool call fails
EventWhen it fires
SessionStartWhen a new session begins
SessionEndWhen a session ends
SetupEarly in session setup
StopWhen Claude stops generating
StopFailureWhen a stop fails
UserPromptSubmitWhen the user submits a prompt
EventWhen it fires
SubagentStartWhen a subagent session starts
SubagentStopWhen a subagent session stops
PreCompactBefore context compaction
PostCompactAfter context compaction
EventWhen it fires
PermissionRequestWhen Claude requests permission for a tool
PermissionDeniedWhen a permission request is denied
NotificationWhen a notification is emitted
EventWhen it fires
FileChangedWhen a file changes on disk
CwdChangedWhen the working directory changes
InstructionsLoadedWhen CLAUDE.md instructions are loaded
ConfigChangeWhen settings change
WorktreeCreateWhen a git worktree is created
WorktreeRemoveWhen a git worktree is removed

Hook types

Each hook entry specifies a type field that determines how it runs.
Runs a shell command. The most common hook type.
{
  "type": "command",
  "command": "my-audit-script.sh",
  "shell": "bash",
  "timeout": 10,
  "statusMessage": "Running audit...",
  "async": false
}
FieldTypeDescription
commandstringShell command to execute
shell"bash" | "powershell"Shell interpreter (default: bash)
timeoutnumberTimeout in seconds for this command
statusMessagestringCustom spinner message while the hook runs
asyncbooleanRun in background without blocking (default: false)
asyncRewakebooleanBackground hook that wakes the model on exit code 2
oncebooleanRun once and remove after execution
ifstringPermission-rule syntax to conditionally run (e.g. "Bash(git *)")

Configuration in settings.json

Hooks are nested under the hooks key. Each top-level key is a hook event; its value is an array of matcher objects:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "/usr/local/bin/audit-bash.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          { "type": "command", "command": "prettier --write $TOOL_OUTPUT_FILE", "async": true }
        ]
      }
    ]
  }
}
Each matcher object has:
FieldTypeDescription
matcherstringTool name or pattern to match (e.g. "Bash", "Write"). Omit to match all tools
hooksobject[]One or more hook definitions to run when the matcher matches

Hook execution flow

1

Tool call initiated

Claude decides to call a tool (e.g. Bash(git status)).
2

PreToolUse hooks run

All matching PreToolUse hooks execute in order. Hook receives the tool name and input as JSON via environment variables.
3

Tool executes

If no hook blocked the call, the tool runs.
4

PostToolUse hooks run

All matching PostToolUse hooks execute. Hook receives the tool output in addition to name and input.

Hook environment

For command hooks, the following environment variables are available:
VariableValue
CLAUDE_TOOL_NAMEName of the tool being called (e.g. Bash)
CLAUDE_TOOL_INPUTFull JSON input to the tool
CLAUDE_HOOK_EVENTEvent name (e.g. PreToolUse)

Blocking a tool call

A PreToolUse hook can block a tool call by exiting with a non-zero exit code. Claude receives an error and does not execute the tool.
#!/usr/bin/env bash
# Block any rm -rf command
if echo "$CLAUDE_TOOL_INPUT" | grep -q "rm -rf"; then
  echo "Blocked: rm -rf is not allowed" >&2
  exit 1
fi

Example hooks

{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "logger -t claude-code \"[PreToolUse] $CLAUDE_TOOL_NAME: $CLAUDE_TOOL_INPUT\""
          }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# ~/.claude/hooks/block-dangerous.sh
DANGEROUS_PATTERNS="rm -rf|sudo rm|chmod -R 777|> /etc|dd if="

if echo "$CLAUDE_TOOL_INPUT" | grep -qE "$DANGEROUS_PATTERNS"; then
  echo "Blocked: dangerous command pattern detected" >&2
  exit 1
fi
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://hooks.example.com/claude-session-end",
            "headers": {
              "Authorization": "Bearer $WEBHOOK_TOKEN"
            },
            "allowedEnvVars": ["WEBHOOK_TOKEN"]
          }
        ]
      }
    ]
  }
}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are a security reviewer. The following is a bash command about to be run by Claude Code. Respond with {\"ok\": true} if it is safe, or {\"ok\": false, \"reason\": \"...\"} if it should be blocked.\n\n$ARGUMENTS",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

Hook sources

Hooks can come from multiple sources, resolved in priority order:
SourceLocation
User settings~/.claude/settings.json
Project settings.claude/settings.json
Local settings.claude/settings.local.json
Plugin hooks~/.claude/plugins/*/hooks/hooks.json
Session hooksIn-memory, temporary
Built-in hooksRegistered internally by Claude Code
When allowManagedHooksOnly: true is set in managed settings, only hooks from the managed policy file run. User, project, and local hooks are ignored.

Disabling hooks

To disable all hooks and status line execution for a session or globally:
{
  "disableAllHooks": true
}
You can also use bare mode (--bare CLI flag or CLAUDE_CODE_SIMPLE=1) to skip hooks entirely along with other non-essential features.

Build docs developers (and LLMs) love