Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/VineeTagarwal-code/claude-code/llms.txt

Use this file to discover all available pages before exploring further.

Hooks let you run your own code at key points during a Claude Code session. You can use hooks to log tool usage, send notifications, enforce policies, block dangerous commands, or integrate with external systems — all without modifying Claude’s behavior directly.

How hooks work

A hook is a command (or HTTP request, or LLM prompt) associated with a lifecycle event. When the event fires, Claude Code runs your hook and optionally uses its output to influence behavior. For example:
  • A PreToolUse hook can block a Bash command before it runs by exiting with code 2
  • A PostToolUse hook can log every file write to an audit trail
  • A Notification hook can send a desktop notification when Claude finishes working
  • A Stop hook can verify that tests pass before Claude completes a task

Hook events

The following events are available:
EventWhen it fires
PreToolUseBefore Claude runs any tool. Can block the tool by returning a non-zero exit code
PostToolUseAfter a tool succeeds
PostToolUseFailureAfter a tool fails
NotificationWhen Claude Code emits a notification (for example, task complete or waiting for input)
UserPromptSubmitWhen you submit a message to Claude
SessionStartWhen a session begins
SessionEndWhen a session ends
StopWhen Claude finishes a response. Can block completion by exiting with code 2
StopFailureWhen a Stop hook blocks completion and Claude retries
SubagentStartWhen a subagent (background task) starts
SubagentStopWhen a subagent completes
PreCompactBefore context compaction runs
PostCompactAfter context compaction completes
PermissionRequestWhen Claude needs permission to use a tool (headless/async agents only)
PermissionDeniedWhen a tool use is denied
SetupOn Claude Code startup, before the session begins
TeammateIdleWhen a teammate agent becomes idle
TaskCreatedWhen a background task is created
TaskCompletedWhen a background task completes
ElicitationWhen Claude requests additional information from the user
ElicitationResultAfter an elicitation request is answered
ConfigChangeWhen settings change during a session
WorktreeCreateWhen a git worktree is created
WorktreeRemoveWhen a git worktree is removed
InstructionsLoadedWhen CLAUDE.md instruction files are loaded
CwdChangedWhen the working directory changes
FileChangedWhen a file is modified on disk

Hook configuration

Hooks are configured under the hooks key in any settings file. The structure is:
{
  "hooks": {
    "EventName": [
      {
        "matcher": "optional-pattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-script.sh"
          }
        ]
      }
    ]
  }
}
Each event maps to an array of matchers. A matcher groups one or more hook commands under an optional pattern. The matcher field uses the same permission rule syntax as tool rules — for example, "Bash(git *)" matches only Bash commands that start with git.

Hook types

Command hook

Runs a shell command. This is the most common hook type.
{
  "type": "command",
  "command": "bash /path/to/my-script.sh"
}
type
"command"
required
Must be "command".
command
string
required
Shell command to execute. Runs via your default shell (bash unless overridden by the shell field).
shell
"bash" | "powershell"
Shell interpreter. Defaults to bash.
if
string
Permission rule syntax to filter when this hook runs, for example "Bash(git *)". Only runs if the tool call matches the pattern. Evaluated before spawning the process.
timeout
number
Timeout in seconds for this specific command. Overrides the global hook timeout.
statusMessage
string
Custom message to show in the spinner while the hook is running.
async
boolean
When true, the hook runs in the background without blocking Claude’s response.
once
boolean
When true, the hook runs once and is automatically removed after execution.

HTTP hook

POSTs the hook input as JSON to an HTTP endpoint.
{
  "type": "http",
  "url": "https://hooks.example.com/claude-events",
  "headers": {
    "Authorization": "Bearer $MY_TOKEN"
  },
  "allowedEnvVars": ["MY_TOKEN"]
}
type
"http"
required
Must be "http".
url
string
required
URL to POST the hook input JSON to.
headers
object
Additional HTTP headers. Values may reference environment variables using $VAR_NAME syntax. Only variables listed in allowedEnvVars are interpolated.
allowedEnvVars
string[]
Explicit list of environment variable names that may be interpolated into header values. Required for env var interpolation to work.
if
string
Permission rule syntax filter.
timeout
number
Timeout in seconds.

Prompt hook

Evaluates a prompt using a language model. The model’s response determines whether the hook allows or blocks the action.
{
  "type": "prompt",
  "prompt": "Verify that the following file write does not contain secrets: $ARGUMENTS"
}
type
"prompt"
required
Must be "prompt".
prompt
string
required
Prompt to evaluate. Use $ARGUMENTS as a placeholder for the hook input JSON.
model
string
Model to use for this prompt hook. Defaults to a small fast model.
if
string
Permission rule syntax filter.
timeout
number
Timeout in seconds.

Agent hook

Runs an agentic verification using a multi-turn LLM query. The agent must call a structured output tool with { "ok": true } or { "ok": false, "reason": "..." }.
{
  "type": "agent",
  "prompt": "Verify that unit tests ran and all passed. Check the output of the last Bash tool use."
}
type
"agent"
required
Must be "agent".
prompt
string
required
Prompt describing what to verify. Use $ARGUMENTS placeholder for hook input JSON.
model
string
Model to use. Defaults to Haiku.
timeout
number
Timeout in seconds. Defaults to 60.

Hook input and output

Input

Claude Code passes context to your hook via stdin as a JSON object. The shape varies by event, but always includes:
{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m 'fix: update config'"
  }
}
For PostToolUse, the input also includes the tool output:
{
  "session_id": "abc123",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/tmp/output.txt",
    "content": "hello"
  },
  "tool_response": "File written successfully"
}

Output and exit codes

Your hook communicates back to Claude Code via stdout (for structured output) and exit code:
Exit codeMeaning for PreToolUseMeaning for Stop
0Allow the tool to runAllow completion
2Block the tool; show stdout to Claude as an errorBlock completion; Claude retries with stdout as feedback
otherError; tool runs anyway (non-blocking)Non-blocking error
When a PreToolUse hook exits with code 2, the stdout is sent back to Claude as the tool error message. Claude then decides how to proceed based on that message.

Using /hooks to configure hooks

Run /hooks in a Claude Code session to open the hooks management panel. From there you can:
  • View all configured hooks grouped by event and source
  • Add new hooks interactively
  • Remove hooks you no longer want

Example: logging all tool uses

This hook appends every tool use to a log file:
{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{event: .hook_event_name, tool: .tool_name, time: now | todate}' >> ~/.claude/tool-log.jsonl"
          }
        ]
      }
    ]
  }
}

Example: blocking dangerous Bash commands

This hook blocks rm -rf commands before they run:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.command' | grep -q 'rm -rf'; then echo 'Blocked: rm -rf is not allowed'; exit 2; fi"
          }
        ]
      }
    ]
  }
}
The full hook input JSON is available as stdin, not as $CLAUDE_TOOL_INPUT. Read it with cat or pipe from stdin: read -r input; echo \"$input\" | jq -r '.tool_input.command'.
A cleaner version using stdin:
#!/usr/bin/env bash
# hooks/check-bash.sh
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

if [[ "$command" == *"rm -rf"* ]]; then
  echo "Blocked: rm -rf commands are not allowed in this project."
  exit 2
fi
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/check-bash.sh"
          }
        ]
      }
    ]
  }
}

Example: desktop notification when Claude finishes

This hook sends a desktop notification when Claude completes a response (macOS):
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"'",
            "async": true
          }
        ]
      }
    ]
  }
}
For Linux with notify-send:
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude finished'",
            "async": true
          }
        ]
      }
    ]
  }
}

Example: verify tests pass before completing

This agent hook verifies that the previous Bash command ran tests and all passed:
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Check the conversation transcript. If Claude just ran tests, verify that all tests passed. If any tests failed, return ok: false with the failure summary. If no tests were run, return ok: true.",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Hook sources and precedence

Hooks are collected from multiple sources and merged:
SourceFileDisplay name
User settings~/.claude/settings.jsonUser
Project settings.claude/settings.jsonProject
Local settings.claude/settings.local.jsonLocal
Managed settingsPlatform-specific managed pathManaged
All hooks from all sources run for each event, in source precedence order (local → project → user → managed).

Disabling hooks

To disable all hooks for a session or project, set disableAllHooks: true in your settings:
{
  "disableAllHooks": true
}
Enterprise administrators can restrict hooks to managed settings only:
{
  "allowManagedHooksOnly": true
}
When allowManagedHooksOnly is set in managed settings, only hooks defined in the managed settings file are run. User, project, and local hooks are ignored.

Enterprise HTTP hook controls

Administrators can restrict which URLs HTTP hooks may target:
{
  "allowedHttpHookUrls": [
    "https://hooks.example.com/*",
    "https://audit.internal.company.com/claude-events"
  ],
  "httpHookAllowedEnvVars": ["AUTH_TOKEN", "TENANT_ID"]
}
  • allowedHttpHookUrls — only HTTP hooks targeting matching URLs are permitted. Supports * as a wildcard. If set to an empty array, no HTTP hooks are allowed.
  • httpHookAllowedEnvVars — allowlist of environment variable names that HTTP hooks may interpolate into headers. If set, a hook’s effective allowedEnvVars is restricted to the intersection with this list.