AnswerQA

Which hook event should I use? The full list, ranked.

Answer

Claude Code exposes 28 hook events across five categories: lifecycle, tool execution, turn control, agent teams, and context management. Exit code 2 blocks the action on blocking events. Non-blocking events show stderr but cannot prevent what already happened.

By Kalle Lamminpää Verified May 12, 2026

Hooks are shell commands (or HTTP endpoints, or prompts) that the harness runs at named points in Claude’s execution. The most important thing to know before picking an event: exit code 2 blocks the action on blocking events, but has no effect on non-blocking events where the action already completed.

The four-question decision tree

  1. Do you need to prevent something from happening? Use a blocking event: PreToolUse, UserPromptSubmit, Stop, PreCompact, PermissionRequest, PostToolBatch, TaskCreated, TaskCompleted, SubagentStop, TeammateIdle, ConfigChange, WorktreeCreate, Elicitation, ElicitationResult.

  2. Do you need to react after something happened? Use a non-blocking event: PostToolUse, PostToolUseFailure, SessionStart, SessionEnd, Setup, Notification, SubagentStart, CwdChanged, FileChanged, PostCompact, InstructionsLoaded, PermissionDenied, WorktreeRemove, StopFailure.

  3. Is your hook specific to one tool or a group of tools? Set a matcher on PreToolUse or PostToolUse. Most other events have no matcher support.

  4. Does your hook need to run once (session init) or on every tool call? SessionStart fires once per session. PreToolUse fires before every tool call that matches your matcher.

All 28 events

Lifecycle

EventFires whenBlocking?Common use
SessionStartNew session, resume, /clear, compactNoLoad dev context, print git status
Setup--init-only, --init, --maintenanceNoOne-time dependency install, repo setup
SessionEndSession terminates (clear/resume/logout)NoCleanup, final logging
ConfigChangeA settings file changes on diskYesBlock unauthorized policy changes
CwdChangedDirectory changes via cd commandNodirenv allow, activate virtualenv
FileChangedA watched file changesNoReload on .env change
InstructionsLoadedCLAUDE.md or rules file loadedNoAudit instruction access

Tool execution

EventFires whenBlocking?Common use
PreToolUseBefore any tool executesYesBlock rm -rf, enforce path rules
PostToolUseTool succeedsNoLint after Edit/Write, validate output
PostToolUseFailureTool failsNoLog failures, notify on error
PostToolBatchParallel batch of tool calls resolvesYesValidate all batch results before continuing
PermissionRequestPermission dialog appearsYesAuto-approve safe read-only commands
PermissionDeniedTool denied by auto classifierNoAllow Claude to retry (set retry: true in JSON output)

Turn control

EventFires whenBlocking?Common use
UserPromptSubmitUser submits a promptYesAdd git context, filter harmful prompts
UserPromptExpansionSlash command expandsYesGate skill access by user role
StopClaude finishes respondingYesGate on tests passing before Claude declares done
StopFailureTurn ends due to API errorNoLog API errors, send alert
NotificationClaude sends a notificationNoLog notifications to external system

Agent and task events

EventFires whenBlocking?Common use
SubagentStartA subagent is spawnedNoMonitor agent creation
SubagentStopA subagent finishesYesPrevent subagent stop to extend its work
TeammateIdleAn agent-teams teammate goes idleYesKeep teammate working, assign next task
TaskCreatedTask created via TaskCreateYesValidate task description before creation
TaskCompletedTask marked completeYesEnforce pre-completion checks

Context management

EventFires whenBlocking?Common use
PreCompactBefore context compactionYesBlock premature compaction
PostCompactAfter compaction completesNoLog tokens removed

MCP elicitation

EventFires whenBlocking?Common use
ElicitationMCP server requests user inputYesAuto-respond or gate MCP user input
ElicitationResultUser responds to an elicitationYesValidate or transform user response

Worktrees

EventFires whenBlocking?Common use
WorktreeCreateA worktree is createdYesCustom worktree setup scripts
WorktreeRemoveA worktree is removedNoCleanup worktree state

Exit code semantics

Exit 0: allow action, or success (non-blocking)
Exit 1: treated as allow (non-blocking error)
Exit 2: BLOCK action (blocking events) OR show stderr only (non-blocking)
Other:  allow / continue with non-blocking error

Stop uses a different protocol than most blocking events. Return JSON on stdout:

{
  "decision": "block",
  "reason": "Tests are failing. Fix before declaring done."
}

Without the JSON protocol, the Stop hook output is treated as a stop-hook-error subtype and Claude reads it as an error to diagnose.

Key payload fields

PreToolUse / PostToolUse:

{
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"},
  "tool_use_id": "abc123",
  "tool_response": "..."
}

UserPromptSubmit:

{
  "prompt": "the user's prompt text"
}

SessionStart:

{
  "source": "startup|resume|clear|compact",
  "model": "claude-sonnet-4-6"
}

ConfigChange:

{
  "config_source": "user_settings|project_settings|local_settings|policy|skills"
}

Footguns

PostToolUse exit code 2 does not undo the tool. The edit or bash command already ran. Exit 2 on PostToolUse shows stderr to Claude as feedback but cannot reverse what happened. If you need to prevent something, use PreToolUse.

UserPromptSubmit has no matcher support. It fires on every prompt regardless of content. A hook that runs a slow script on every prompt adds that latency to every turn. Keep UserPromptSubmit hooks fast, or use PreToolUse with a specific matcher if you only care about tool invocations.

Stop hook gaming. A Stop hook that exits 2 when tests fail causes Claude to keep working. But Claude can resolve the failing test by editing the test assertion itself, which technically makes the tests pass. Gating on tests gets compliance, not correctness. Combine with a PreToolUse hook that denies edits to test files if you want to prevent this.

PostToolBatch blocks the entire agentic loop, not just one call. Exit code 2 on PostToolBatch stops forward progress for the entire parallel batch. Use it for cases where a bad result in any one call should halt everything, not for per-call validation (use PostToolUse for that).

Matcher mode mismatch between list and regex. If your PreToolUse matcher is a comma-separated list ("Edit,Write"), it matches exact tool names. If it is a regex, it is a pattern match. Mixing these by accident is a common source of hooks that fire unexpectedly or not at all. Verify matcher type by checking whether your matcher string contains regex metacharacters.

When NOT to write a hook

  • For something permissions.deny can already express. PreToolUse to block rm -rf is redundant if you have Bash(rm -rf *) in deny rules. Deny rules run before hooks.
  • For formatting on every file write. A slow formatter on every PostToolUse adds latency to every multi-file refactor. Run formatting once at Stop instead.
  • For instrumentation in read-only sessions. Hooks add noise to exploratory or audit-only sessions where Claude makes no changes. Gate hooks on tool type.

Sources

Was this helpful?