This article examines the public mirror’s March 31, 2026 source snapshot. The mirror is not an official Anthropic source release. Implementation claims concern this fixed snapshot, not necessarily the current product release; official documentation establishes product or API contracts.

A hook configuration tells you which script runs, but not why its result matters. To understand the behavior, follow the event to the code that consumes its output: prompt processing, permission checks, tool execution, turn completion, or conversation compaction.

Reading goal: identify what each hook receives, how matching selects it, how stdout becomes a structured result, and which part of the current request that result is allowed to change.

shape-level hook event
  -> getMatchingHooks(settings, plugin, skill, session)
  -> execute matched hook handlers
  -> parse stdout as protocol result
  -> { decision, additionalContext, updatedInput? }

PreToolUse runs before permission/execution.
PostToolUse runs after the tool result exists.
Stop and Compact run at turn or memory boundaries.
Source shape: a hook matters because its parsed output returns to a specific execution step.

1. Events describe different execution steps

HOOK_EVENTS includes UserPromptSubmit, PreToolUse, PostToolUse, PermissionRequest, SessionStart, Stop, PreCompact, PostCompact, and subagent events. Some run before work, some after work, and some when a session is reconstructed. They are not interchangeable notifications.

Every event receives common fields such as session_id, transcript path, working directory, permission mode, and optional agent identity. Event-specific schemas add the data needed at that moment: tool hooks receive the tool name, input, and id; Stop receives the last assistant message; PreCompact receives the trigger and custom instructions, while PostCompact receives the generated summary.

EventInput available to the hookPossible effect
UserPromptSubmitThe submitted prompt.Stop the query or add a separate context message.
PreToolUseTool name, input, and tool-use id.Modify input, add context, or propose allow, ask, or deny.
PostToolUseThe tool request and result.Add context; MCP tool output may also be replaced.
SessionStartThe source: startup, resume, clear, or compact.Add initial context, a user message, or watched paths.
Stop / compact eventsTurn completion or compaction details.Continue or stop, adjust compact instructions, or restore context.

2. Hooks are collected, matched, and checked before execution

getHooksConfig() combines saved settings, registered hooks, session hooks, and session function hooks for the current event. A managed-only policy can exclude plugin and session hooks. getMatchingHooks() then chooses a matching field based on the event: tool name for tool events, source for SessionStart, trigger for compact, notification type for notifications, and agent type for subagent events.

Matchers support exact strings, pipe-separated alternatives, and regular expressions. Tool-related events may also use if conditions such as Bash(git *). Before execution, Claude Code removes duplicates by source and checks global hook disablement, simple mode, and workspace trust. A configured hook does not automatically run in every environment.

3. PreToolUse and PostToolUse have different authority

checkPermissionsAndCallTool() runs PreToolUse before the main permission decision and before the tool changes local state. The hook can propose modified input, add context, stop execution, or return a permission preference. That preference is not the final decision.

resolveHookPermissionDecision() explicitly preserves deny and ask rules from settings. A hook's allow result cannot override them. A tool requiring interaction uses the normal permission path unless the hook supplies updated input that satisfies that interaction; requireCanUseTool still forces the normal path. Otherwise hook allow uses a rule-and-tool check and may return without traversing the entire normal permission stack. Multiple hook results use the priority deny > ask > allow.

This checkRuleBasedPermissions() helper still parses the input and runs tool.checkPermissions(), preserving tool denials, content-specific ask rules, and safetyCheck asks. It skips the full mode and default-allow flow; its checks extend beyond settings rules.

Permission checks applying deny and ask rules after a PreToolUse hook proposal
A hook supplies one input to permission handling; configured restrictions still decide whether the tool may run.

PostToolUse runs after a result exists. It can return a blocking error or additional context for the next model request. updatedMCPToolOutput is deliberately narrower: Claude Code applies it only when the completed tool is an MCP tool, so an ordinary built-in tool cannot have its result silently replaced through this field.

4. Stdout is parsed as a small protocol

A command hook writes to stdout, but Claude Code does not treat every line as an instruction. parseHookOutput() trims the output and attempts JSON parsing only when it begins with {. Valid JSON is checked against hook output schemas and translated into fields such as preventContinuation, permission behavior, updated input, additional context, MCP output replacement, and retry.

Non-JSON command output also has defined status handling. Exit code 0 is successful, exit code 2 produces blocking feedback, and other nonzero codes are shown as non-blocking errors. For JSON output, {"continue": false} stops the current step and can carry a stopReason. Structured fields, not arbitrary prose, determine what the runtime changes.

4.1 Accepted, completed, and delivered are separate states

A command hook can request background execution through its async configuration or by writing {"async":true} as the first stdout line. forceSyncExecution prevents that transfer. The returned backgrounded: true and success status mean the runtime accepted ownership of the background process, not that its work succeeded.

async configuration / first line {"async":true}
  → registered in background: foreground step continues
  → process completed: read final output
  → later main-thread attachment collection
  → systemMessage / additionalContext enter model messages
Accepted ≠ successfully completed ≠ delivered to the model
Ordinary asynchronous results arrive through later attachment collection, not through an already-finished permission gate.

The registry waits for completed, skips the async marker while extracting a response, marks the response as collected, and removes the entry. Running, killed, or empty-output processes take separate paths. This is an in-process Map, not a durable queue that reconstructs jobs after restart. Attachment collection produces async_hook_response; message conversion turns only systemMessage and additionalContext into model context. It does not replay a permission decision or undo a tool.

asyncRewake is a separate route: it bypasses the registry and enqueues a task notification when the process finishes with exit code 2. That may wake an idle model or enter queued messages during a query. Ordinary async hooks do not inherit this active wakeup behavior.

OutputRuntime interpretation
Plain stdoutDisplay or context material according to the event, not an automatic permission decision.
Non-JSON exit code 2Blocking feedback; the event’s caller decides which step is blocked.
continue: falseStop continuation with an optional stop reason.
updatedInputReplace input through allow/ask or passthrough handling.
additionalContextA separate hook-context attachment.
async: trueBackground acceptance, not completion.

5. Prompt, session, stop, and compact hooks change different moments

UserPromptSubmit runs before the model query. A blocking result sets shouldQuery to false; additional context becomes a separate hook_additional_context message instead of being disguised as user text.

SessionStart is not limited to process startup. Its source may be startup, resume, clear, or compact. The handler can collect context, an initial user message, and file-watch paths whenever the session becomes ready to continue.

Stop runs after the assistant turn settles, not after each tool. These outcomes differ: continue: false becomes preventContinuation, emits hook_stopped_continuation, and stops the loop. A blocking error instead adds feedback to messages and runs another model turn with stopHookActive: true. SubagentStop provides the corresponding event for child loops.

Full compaction has three related hook moments. PreCompact can add instructions before the summary is generated. After the summary and important attachments are restored, Claude Code runs SessionStart with source: compact so session-level information can be added again. PostCompact runs after the replacement message components have been prepared, but before compactConversation() returns them. The query loop installs the returned messages afterward.

PreCompact before summary generation, recovery material and SessionStart results prepared before PostCompact, then message installation
PreCompact precedes summarization. SessionStart results join the recovery material; PostCompact runs before the query loop installs the returned messages.

The distinction is visible in the query-loop branch: stopping continuation ends the query, whereas blocking completion requests further work. Hook output must be interpreted at its consuming event.

6. Choose a hook by the action you need

GoalHookRestriction that still applies
Add project context before a query.UserPromptSubmit or SessionStartThe content is marked as hook context, not user-authored text.
Validate or adjust tool input.PreToolUseInitial input passes schema and semantic validation; modified input follows the selected permission path. Full semantic validation is not guaranteed to run again.
Add guidance after a tool result.PostToolUseOnly MCP tool output can be replaced.
Make an automated permission decision.PermissionRequestAn unanswered headless request is denied.
Act when a turn or child agent ends.Stop or SubagentStopThis concerns the whole loop, not one tool result.

The useful rule is to read a hook name together with the code that consumes its result. The same script mechanism has different authority before a prompt, before a tool, after a result, or during compaction.

Sources

The source-code claims in this article are based on the public mirror and the linked official documentation. Server-side model scheduling is not inferred from client-side hooks.