A tool call is denied by the auto mode classifier (only fires in auto mode, not on manual user denials)
No
Audit classifier denials, return retry: true to let model retry
Compaction (context management):
Event
When It Fires
Can Block?
Use Case
PreCompact
Before context compaction
Yes
Block unwanted auto-compact, save state
PostCompact
After context compaction completes
No
Restore state, log compaction
Multi-agent (orchestration):
Event
When It Fires
Can Block?
Use Case
SubagentStart
Sub-agent spawned
No
Subagent initialization
SubagentStop
Sub-agent finishes
Yes
Subagent cleanup
TeammateIdle
Agent team member about to go idle
Yes
Team coordination, quality gates
TaskCreated
Task created via TaskCreate
Yes
Enforce naming, block disallowed tasks
TaskCompleted
Task being marked as completed
Yes
Enforce completion criteria
Configuration (settings & instructions):
Event
When It Fires
Can Block?
Use Case
ConfigChange
Config file changes during session
Yes (except policy)
Enterprise audit, block unauthorized changes
InstructionsLoaded
A CLAUDE.md or .claude/rules/*.md file is loaded into context (at session start and when files are lazily loaded mid-session)
No
Audit which instruction files are active, compliance tracking
File system (workspace changes):
Event
When It Fires
Can Block?
Use Case
CwdChanged
Working directory changes (e.g. when Claude executes cd). Useful with direnv
No
Reload env vars, activate toolchains
FileChanged
A watched file changes on disk; matcher specifies which filenames to watch
No
Reload config, trigger watchers
WorktreeCreate
A worktree is being created via --worktree or isolation: "worktree". Replaces default git behavior
Yes (non-zero exit)
Custom VCS setup (SVN, Perforce)
WorktreeRemove
A worktree is being removed (at session exit or when a subagent finishes)
No
Clean up VCS state
User interaction (prompts & notifications):
Event
When It Fires
Can Block?
Use Case
UserPromptSubmit
User submits prompt, before Claude processes it
Yes
Context enrichment, prompt validation
UserPromptExpansion
A slash command expands into a prompt, before it reaches Claude
Yes
Block a command from running, inject skill context
Notification
Claude sends notification
No
Sound alerts, custom notifications
MessageDisplay
While assistant message text is displayed (display-only)
No
Strip markdown on screen, redact secrets before rendering
Elicitation
An MCP server requests user input during a tool call
Yes
Respond programmatically, skip the interactive dialog
ElicitationResult
User responds to an MCP elicitation, before the response is sent back to the server
Yes
Audit or override responses before they reach the MCP server
Stop and SubagentStop: last_assistant_message field (v2.1.47+): These events include a last_assistant_message field in their JSON input, giving direct access to Claude’s final response without parsing transcript files. Useful for orchestration pipelines that need to inspect or log the last output.
Block Claude on errors (exit code 2 is ignored for blocking decisions)
Provide real-time feedback via stdout or systemMessage
Guarantee execution order with other hooks
Async hooks CAN return additionalContext via JSON output. If the hook produces hookSpecificOutput.additionalContext, that content is delivered to Claude as context on the next conversation turn (after the background process exits). It is not real-time, but it does reach Claude.
Use async only when the hook’s completion is truly independent of Claude’s workflow.
For hooks that run long background work and need to surface a failure back to Claude, use asyncRewake: true instead of async: true. Like async, the hook runs in the background without blocking. Unlike async, if the hook exits with code 2, Claude Code wakes the session immediately and shows the hook’s stderr as a system reminder so Claude can react to the failure even if the session was otherwise idle.
{
"type": "command",
"command": ".claude/hooks/deploy-watcher.sh",
"asyncRewake": true
}
Use this for background monitoring tasks (deploy pipelines, CI status) where a failure should interrupt Claude rather than silently disappear into the log.
Regex pattern filtering when hooks fire (tool name, session start reason, etc.)
if
Permission-rule filter controlling when the hook fires (e.g. Bash(git *)) — v2.1.85+
type
Hook type: "command", "http", "mcp_tool", "prompt", or "agent"
command
Shell command to run (for command type)
args
string[] — exec form: array of strings spawned directly without a shell. Path placeholders need no quoting. When present, command is ignored. Use to avoid shell-injection risks. (v2.1.139)
prompt
Prompt text for LLM evaluation (for prompt/agent types). Use $ARGUMENTS as placeholder for hook input JSON
timeout
Max execution time in seconds (default: 600s command, 30s prompt, 60s agent)
model
Model to use for evaluation (for prompt/agent types). Defaults to a fast model
async
If true, runs in background without blocking (for command type only)
asyncRewake
If true, runs in background but wakes Claude on exit code 2 (implies async). Use for background monitoring tasks that need to interrupt Claude on failure
statusMessage
Custom spinner message displayed while hook runs
once
If true, runs only once per session then is removed (skills only)
Exec form (args): Use args: ["program", "arg1", "arg2"] to spawn the command without a shell interpreter. Useful when paths contain spaces or special characters that would require quoting in command. Exec form also avoids shell injection risks in automated contexts.
Hooks do not have to be persisted in settings.json. Claude Code supports ephemeral session-scoped hooks that are registered at runtime and last only for the duration of the current session. They are never written to any config file and disappear when the session ends.
This is the mechanism skills use internally: when you invoke a skill, it can register one or more hooks for that invocation without permanently modifying your configuration. Once the skill finishes (or the session ends), those hooks are gone.
When to use session-scoped hooks:
Skills that need event callbacks only while they are active
Temporary automation (e.g., “audit every file I edit during this session only”)
CI pipelines or orchestration scripts that inject hooks via the API programmatically
Session-scoped hooks follow the same JSON schema as settings.json hooks (same event names, matchers, types, and output format) and can be registered through the programmatic API or by skills at invocation time.
Type /hooks in Claude Code to open a read-only browser for all configured hooks. The menu groups hooks by event, shows the matcher and handler details for each, and labels the source of every hook: [User] (~/.claude/settings.json), [Project] (.claude/settings.json), [Local] (.claude/settings.local.json), [Plugin], or [Session] (runtime-registered). Use it to verify that a hook is actually registered, check which settings file it came from, or inspect the full command or URL without digging through JSON. The menu is read-only: edit the settings JSON directly (or ask Claude) to make changes.
Hook types:
command: Runs a shell command. Receives JSON on stdin, returns JSON on stdout. Most common type.
http(v2.1.63+): POSTs JSON to a URL and reads JSON response. Useful for CI/CD webhooks and stateless backend integrations without shell dependencies. Configure with url and optional allowedEnvVars for header interpolation.
mcp_tool: Calls a tool on an already-connected MCP server. Configure with server (server name) and tool (tool name); the tool’s text output is treated like command stdout. The server must already be connected before the hook fires.
prompt: Sends prompt + hook input to a Claude model (Haiku by default) for single-turn evaluation. Returns {ok: true/false, reason: "..."}. Configure model via model field.
agent: Spawns a subagent with tool access (Read, Grep, Glob, etc.) for multi-turn verification. Returns same {ok: true/false} format. Up to 50 tool-use turns.
HTTP hooks receive the same JSON payload as command hooks and must return valid JSON. The allowedEnvVars field lists environment variables that can be referenced in headers (e.g., for Bearer token authentication).
The if field filters when a hook fires using the same permission-rule syntax as allowedTools. This avoids spawning subprocesses on every event and eliminates the need for shell-side case statements.
// Before: hook fires on every PostToolUse — guard logic inside the script
{
"event": "PostToolUse",
"command": "./scripts/log-tool-usage.sh"
}
// After: hook fires only when Bash executes a git command
{
"event": "PostToolUse",
"if": "Bash(git *)",
"command": "./scripts/log-git-usage.sh"
}
Supported if patterns follow the same syntax as tool permission rules:
Pattern
Fires when
Bash(git *)
Any Bash call starting with git
Edit
Any Edit tool call
Write(/tmp/*)
Write to paths under /tmp/
Bash(npm * | yarn *)
npm or yarn commands
Performance: Every hook spawn is a subprocess. Conditional if filtering reduces overhead in large repos where PostToolUse fires hundreds of times per session.
Common fields (all events): session_id, transcript_path, cwd, permission_mode, hook_event_name. Event-specific fields (like tool_name and tool_input for PreToolUse) are added on top.
Field
Type
Description
session_id
string
Unique session identifier
transcript_path
string
Path to session transcript file
cwd
string
Current working directory
permission_mode
string
Active permission mode
hook_event_name
string
Event that triggered the hook
effort.level
string
Active effort level: low, medium, high, xhigh, max. Bash-type hooks also receive this as $CLAUDE_EFFORT env var. (v2.1.133)
agent_id
string
Unique identifier for the subagent. Present only when the hook fires inside a subagent call.
agent_type
string
Agent name ("Explore", "security-reviewer", etc.). Present when the hook fires inside a subagent or when the session uses --agent.
Hooks communicate results through exit codes and optional JSON on stdout. Choose one approach per hook: either exit codes alone, or exit 0 with JSON for structured control. Claude Code only processes JSON on exit 0, so if your hook exits with any other code, stdout and any JSON it contains are silently discarded.
Universal JSON fields (all events):
Field
Default
Description
continue
true
If false, Claude stops processing entirely
stopReason
none
Message shown to user when continue is false
suppressOutput
false
If true, hides stdout from verbose mode
systemMessage
none
Warning message shown to user
terminalSequence
none
Allowlisted terminal escape string to emit (OSC 0/1/2/9/99/777 or BEL). Use for desktop notifications or window titles instead of writing to /dev/tty, which hooks cannot access. Requires v2.1.141+.
Event-specific decision control varies by event type:
PreToolUse: Uses hookSpecificOutput with permissionDecision (allow/deny/ask/defer), permissionDecisionReason, updatedInput, additionalContext. When multiple PreToolUse hooks return different decisions, precedence is: deny > defer > ask > allow (v2.1.89+).
TeammateIdle, TaskCompleted: Exit code 2 only (no JSON decision control)
PermissionRequest: Uses hookSpecificOutput with decision.behavior (allow/deny)
continueOnBlock (PostToolUse only, v2.1.139): When true, a decision: "block" response feeds the reason back to Claude as context and continues the turn instead of halting. Use to give Claude a chance to retry with a compliant approach:
{
"type": "PostToolUse",
"matcher": "Write|Edit",
"command": "check-file-policy.sh",
"continueOnBlock": true
}
Without continueOnBlock, a blocked PostToolUse stops the turn and surfaces an error. With it, Claude receives the rejection reason and can self-correct.
Output replacement (PostToolUse, v2.1.121): PostToolUse hooks can replace what Claude receives as the tool result via hookSpecificOutput.updatedToolOutput. Works for all tools: Bash, Read, Write, Edit, MCP tools, etc.:
{
"hookSpecificOutput": {
"updatedToolOutput": "redacted: output contained PII, removed by policy hook"
}
}
Use cases: scrub PII from tool outputs before Claude processes them, compress large results, inject metadata or audit trails into every tool response.
PreToolUse blocking example (preferred over exit code 2):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
When Claude fires AskUserQuestion mid-session, interactive prompts are not available in headless environments (CI pipelines, web frontends, orchestrators). A PreToolUse hook can intercept the question, collect the answer via an external UI, and return it before the tool executes:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { "answer": "yes, proceed with migration" },
"permissionDecision": "allow"
}
}
The hook script is responsible for retrieving the answer (e.g., polling a webhook or reading from a queue). Return updatedInput with the answer and permissionDecision: "allow" to satisfy the question and continue execution without interactive prompts.
defer is designed for headless integrations where Claude is orchestrated by an external process. When a hook returns permissionDecision: "defer", Claude pauses with stop_reason: "tool_deferred" and waits. The calling process can then collect input from a user or another system and resume the session with --resume <session-id>. In interactive terminal sessions, defer is ignored with a warning.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "defer",
"permissionDecisionReason": "Awaiting human approval via external workflow"
# On success: completely silent (nothing enters agent context)
# On failure: surface errors + exit 2 to re-engage agent
OUTPUT=$(bunrunbuild2>&1)
EXIT_CODE=$?
if [[ $EXIT_CODE-eq0 ]]; then
exit0# Silent — no output, no context noise
fi
# Failure: send errors to agent for correction
echo"$OUTPUT">&2
exit2
This asymmetry (silence on success, signal on failure) prevents successful build logs, test output, and lint reports from accumulating as “context noise” in long sessions. The agent only sees what requires action.
These four hook events have access to a CLAUDE_ENV_FILE environment variable, which provides a path to a file where you can persist environment variables for subsequent Bash commands in the session. Write export statements to it (use append >> to preserve variables set by other hooks):
Variables written here are available to all subsequent Bash tool calls Claude makes in that session. This is the standard way to inject environment configuration (e.g., activating nvm, sourcing direnv) without modifying the system environment permanently.
watchPaths (updates dynamic file watch list). The matcher field serves a dual role: it both registers filenames in the watch list AND filters which handler groups run.
WorktreeCreate
name (slug identifier)
stdout prints absolute path to created worktree (or HTTP: hookSpecificOutput.worktreePath). Non-zero exit fails creation.
hookSpecificOutput.action, content (overrides user response)
MessageDisplay
turn_id, message_id, index (0-based batch), final (bool, last batch), delta (new lines)
hookSpecificOutput.displayContent (replaces rendered text on screen, transcript unchanged). Default timeout: 10s.
Notification
message, title, notification_type
None (side effects only)
background_tasks and session_crons are available in Stop/SubagentStop since v2.1.145. Each entry in background_tasks has id, type (shell/subagent/monitor/workflow/teammate/cloud session/MCP task), status, description, and type-specific fields. Each entry in session_crons has id, schedule, recurring (bool), prompt. Use these to distinguish “session done” from “session waiting for background work”.
additionalContext is injected into Claude’s context window as a system reminder at the point where the hook fired. For PostToolUse and PostToolBatch, it appears next to the tool result. For UserPromptSubmit, it appears alongside the submitted prompt. Multiple hooks returning additionalContext for the same event are all delivered. Values are capped at 10,000 characters.
On Windows, Claude Code can use PowerShell as a first-class tool alongside Bash — allowing .ps1 scripts, PowerShell modules, and Windows-native commands without requiring WSL or Git Bash.
Enable it in ~/.claude/settings.json:
{
"tools": {
"powershell": {
"enabled": true
}
}
}
Once enabled, Claude can execute PowerShell commands directly (e.g., Get-ChildItem, Invoke-WebRequest, dotnet CLI). Useful for teams working in Windows-first environments where .ps1 scripts are the standard automation layer.
Preview: This is an opt-in preview as of v2.1.84. The Bash tool remains available on Windows via Git Bash or WSL and is still preferred for cross-platform scripts.
Security hooks are critical for protecting your system.
Advanced patterns: For comprehensive security including Unicode injection detection, MCP config integrity verification, and CVE-specific mitigations, see Security Hardening Guide.
Claude Code Security (research preview): Anthropic offers a dedicated codebase vulnerability scanner that traces data flows across files, challenges findings internally before surfacing them (adversarial validation), and generates patch suggestions. Separate from the Security Auditor Agent above — waitlist access only. See Security Hardening Guide → Claude Code as Security Scanner.
Validated at scale: In a March 2026 partnership with Mozilla, Claude Opus 4.6 scanned ~6,000 C++ files in Firefox’s JS engine in two weeks, surfacing 22 confirmed vulnerabilities (14 high severity) — roughly one fifth of all high-severity Firefox CVEs fixed in 2025. Demonstrates the model’s practical depth for production security work, well beyond surface-level linting.
The Claude Code team uses a pattern where permission requests are routed to a more capable model acting as a security gate, rather than relying solely on static rule matching.
Concept: A PreToolUse hook intercepts permission requests and forwards them to Opus 4.7 (or another capable model) via the API. The gate model scans for prompt injection, dangerous patterns, and unexpected tool usage — then auto-approves safe requests or blocks suspicious ones.
"Analyze this tool call for security risks. Is it safe? Reply SAFE or BLOCKED:reason")
[[ "$VERDICT"== SAFE* ]] && exit0
echo"BLOCKED by security gate: $VERDICT">&2
exit2
Why use a model as gate: Static rules catch known patterns but miss novel attacks. A capable model understands intent and context — it can distinguish rm -rf node_modules (cleanup) from rm -rf / (destruction) based on the surrounding conversation, not just pattern matching.
Trade-off: Each gated call adds latency and cost. Use fast-path exemptions for read-only tools and only gate write/execute operations.
The problem: When Claude compacts context during a long session, agents configured with a specific role — team lead, developer, reviewer — can “forget” their identity. The compacted transcript no longer contains the original system instructions, so the next response drops the role entirely and starts behaving generically.
This is most visible in agent teams with explicit identity prefixes. A developer agent that was consistently marking messages with 🔨 DEVELOPER: suddenly stops after compaction and starts responding as a generic assistant.
The pattern: Store the agent’s identity in a file (.claude/agent-identity.txt). After each user message, a UserPromptSubmit hook checks whether the last assistant response includes the expected identity marker. If not — which happens after compaction — it injects the identity file contents as additionalContext. The next response re-establishes the role without human intervention.
.claude/agent-identity.txt
# Your agent's identity instructions — anything that should survive compaction
Zero overhead when identity marker is present (exits immediately on match)
Silent no-op when no .claude/agent-identity.txt file exists
Triggers automatically after compaction — no manual intervention needed
Works in both solo sessions (long-running agents) and agent team configurations
Customization: Set CLAUDE_IDENTITY_MARKER in your environment to a short, distinctive string from the agent’s standard output (e.g. "LEAD:", "DEVELOPER:", "🔨"). If not set, the hook uses the first 40 characters of the identity file as the marker.
Origin: Pattern sourced from Nick Tune’s hook-driven dev workflows (2026-02-28). The broader article covers state machine workflows with agent teams — see Agent Teams Workflow for context.
As your hook collection grows, a tension emerges: some developers want minimal overhead (fast startup, no blocking checks), while security-conscious members or CI pipelines want strict enforcement. A single settings.json can’t serve both well.
The pattern: gate each hook behind an environment variable that declares the desired enforcement level. Three levels cover most teams:
minimal — only critical safety hooks (secrets detection, permission blocks)
standard — development workflow hooks (format, typecheck, lint)
strict — full enforcement (governance, compliance, MCP health, quality gates)