Hooks
Hooks register lifecycle callbacks inside a session. The registration lifecycle
is registerHook(), hookCount(), and unregisterHook().
Events
The Node.js and Python registration helpers accept these 25 names
(sdk/node/src/hook_bridge.rs); any other string is rejected:
Rust Core also exposes permission_request, pre_compact, and post_compact
as lifecycle points. The Go SDK sends Hook.EventType straight to the Core hook
type, so Go can register these three names as well. The Node.js and Python
parsers reject them, so use a Rust or Go host when policy must intercept
permission or compaction directly.
The perception, memory, planning, reasoning, rate-limit, confirmation, success, and intent events serve specialized harnesses. Registering one is accepted everywhere, but it fires only on runtime paths that emit it. Treat an event as a policy boundary only when the protected runtime path consumes its decision.
Registration Example
A handler may return { action: 'continue' }, { action: 'skip' },
{ action: 'block', reason }, { action: 'retry', reason, delayMs }, or
null/undefined to continue. Validate the specific event path you depend on
before using a hook as a production gate.
Lifecycle Governance
The gating events are pre_tool_use, permission_request, pre_compact,
pre_prompt, pre_planning, and pre_run_control. A handler failure or timeout at one of these
points fails closed. Other events are observational or advisory and continue
when their handler infrastructure fails.
pre_tool_use can replace arguments before the tool reaches confirmation or
side effects:
Core accepts updatedInput, updated_input, args, or a direct argument
object inside modified (and also accepts the Codex-style
hookSpecificOutput wrapper). The rewritten object is validated against the
tool's JSON Schema again. Invalid rewritten arguments are rejected before any
tool side effect.
pre_prompt can return modified.prompt and optional
modified.additionalContext (or additional_context). The resulting prompt,
including the bounded hook-context block, replaces the actual user message
sent to the model. permission_request can return decision: 'allow' or
decision: 'deny'; pre_compact can block compaction. session_start and
session_end provide matching lifecycle observations for host cleanup and
audit.
pre_run_control gates a typed steer or interrupt request before it enters
the active Run inbox. post_run_control observes its durable accepted,
applied, settled, or rejected receipt. A retry with the same request ID and
payload reuses the recorded admission result instead of firing a second gating
decision; a conflicting payload with that ID is rejected. Post-control Hook
results are observational and cannot rewrite an already issued receipt.
Denial Feedback
Use block when retrying the same invocation cannot succeed without changing
the request, arguments, or policy context. Use retry for a temporary
condition and include both a reason and suggested delay:
Python callbacks use delay_ms; Go callbacks return
&code.HookResponse{Action: "retry", Reason: "...", DelayMS: 1000}.
The current invocation is denied rather than automatically scheduled. The
model receives the explanation and explicit retry guidance, while direct SDK
callers receive a structured tool error:
A block response uses the same error type with retryable: false and
retry_after_ms: null. Rust callers that need the retry explanation can use
HookEngine::fire_outcome(), which returns HookOutcome with the retry
reason; HookEngine::fire() returns the simpler HookResult, whose
Retry(u64) keeps only the delay.
Propagation
Delegation and automatic subagent fan-out use the task tool. A child run
inherits the parent session's hook engine unless its own configuration supplies
one, so parent hooks observe child tool calls. When a product depends on hook
behavior across delegated runs, cover that product path with an integration
test.