Turn ends due to API error (rate limit, auth failure)
No
Alert on quota exhaustion, observability
PreToolUse
Before a tool call executes
Yes
Security validation, input modification
PostToolUse
After a tool completes successfully
No
Formatting, logging
PostToolUseFailure
After a tool call fails
No
Error logging, recovery actions
Permissions (approval flow):
Event
When It Fires
Can Block?
Use Case
PermissionRequest
Permission dialog appears
Yes
Custom approval logic
PermissionDenied
A permission is denied
No
Audit denied operations, alert
Compaction (context management):
Event
When It Fires
Can Block?
Use Case
PreCompact
Before context compaction
No
Save state before compaction
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
No
Task monitoring, audit logging
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
CLAUDE.md or instructions file loaded
No
Audit which instruction files are active
File system (workspace changes):
Event
When It Fires
Can Block?
Use Case
CwdChanged
Working directory changes during session
No
direnv reload, toolchain switching
FileChanged
A file is modified during session
No
Reload config, trigger watchers
WorktreeCreate
Worktree being created
Yes (non-zero exit)
Custom VCS setup
WorktreeRemove
Worktree being removed
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
Notification
Claude sends notification
No
Sound alerts, custom notifications
Elicitation
Claude requests information from user (headless)
Yes
Intercept and pre-answer questions in automation
ElicitationResult
Response to an elicitation is received
No
Log or audit user responses to Claude’s questions
Stop and SubagentStop — last_assistant_message field (v2.1.47+): These events now 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.
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", "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)
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.
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.
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)
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
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.
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)