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:

Text
pre_tool_use
post_tool_use
generate_start
generate_end
session_start
session_end
skill_load
skill_unload
pre_prompt
post_response
on_error
on_success
pre_context_perception
post_context_perception
pre_memory_recall
post_memory_recall
pre_planning
post_planning
pre_reasoning
post_reasoning
on_rate_limit
on_confirmation
intent_detection
pre_run_control
post_run_control

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

TypeScript
session.registerHook(
'release-publish-observer',
'pre_tool_use',
{ tool: 'bash', commandPattern: 'npm publish|twine upload|cargo publish' },
{ priority: 50, timeoutMs: 1000 },
() => ({ action: 'continue' }),
);

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:

TypeScript
() => ({
action: 'continue',
modified: {
updatedInput: { file_path: 'approved/release.txt', content: 'ready\n' },
},
});

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:

TypeScript
() => ({
action: 'retry',
reason: 'The policy backend is temporarily unavailable.',
delayMs: 1000,
});

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:

JSON
{
"type": "hook_denied",
"reason": "The policy backend is temporarily unavailable.",
"retryable": true,
"retry_after_ms": 1000
}

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.

Management

TypeScript
console.log(session.hookCount());
session.unregisterHook('release-publish-observer');