Skip to main content
A ready-to-run example is available here!

Overview

Hooks let you observe and customize key lifecycle moments in the SDK without forking core code. Typical uses include:
  • Logging and analytics
  • Emitting custom metrics
  • Auditing or compliance
  • Tracing and debugging

Hook Types

Exit Codes

Command hooks (shell scripts) signal their result through their exit code. Prompt-based hooks and agent-based hooks return a JSON decision instead. The SDK matches the Claude Code hook contract:
  • 0 — success. The operation proceeds. stdout is parsed as JSON for structured output (decision, reason, additionalContext, continue).
  • 2 — block. The operation is denied. For PreToolUse and UserPromptSubmit this rejects the action; for Stop it prevents the agent from finishing and the conversation continues. stderr / reason is surfaced as feedback.
  • Any other non-zero exit code — non-blocking error. success is set to False and the error is logged via HookExecutionEvent, but the operation still proceeds.
Only exit code 2 blocks. Exit code 1 (the conventional Unix failure code) is treated as a non-blocking error. A hook intended to enforce a policy must exit with 2.

Key Concepts

  • Registration points: subscribe to events or attach pre/post hooks around LLM calls and tool execution
  • Isolation: hooks run outside the agent loop logic, avoiding core modifications
  • Composition: enable or disable hooks per environment (local vs. prod)

Execution Modes

Hook definitions support three execution modes: Use the least powerful mode that can make the decision. Command hooks are the most deterministic. Prompt hooks add model judgment with one completion. Agent hooks add an agent loop and tools when the event payload is not enough.

Ready-to-run Example

This example is available on GitHub: examples/01_standalone_sdk/33_hooks
examples/01_standalone_sdk/33_hooks/main.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Hook Scripts

The example uses external hook scripts in the hook_scripts/ directory:

Prompt-based Hooks

Set type="prompt" to evaluate a hook event with one LLM completion. Prompt hooks are useful when a decision needs semantic judgment but all required context is already present in the HookEvent payload. For example, a PreToolUse policy can evaluate the intent of a terminal command without starting a tool-using sub-agent.
Key fields on a prompt HookDefinition:
  • name — identifies the hook in logs, events, and its stable prompt-hook:<name> metrics bucket.
  • prompt — the trusted policy used to evaluate each matching event.
  • timeout — the timeout applied to the copied hook LLM.
The hook uses the conversation’s current LLM, including changes made through model or profile switching. The executor copies that LLM so the hook has an isolated timeout, usage ID, and metrics. Hook spend is merged back into the parent conversation’s metrics. The SDK selects Chat Completions or the Responses API from the model’s capabilities. Prompt hooks are single-shot and non-streaming, regardless of the parent LLM’s streaming setting. The policy is placed in system context. The serialized event is sent in a separate user message and marked as untrusted data, so instructions embedded in tool input or output are not treated as hook policy. The model is asked to return the shared hook result contract:
If the conversation has no LLM, the provider call fails, or the response does not contain a valid decision, the hook falls open with decision="allow" and success=False. This lets consumers distinguish an execution failure from a deliberate allow verdict.
Prompt hooks cannot inspect files, run commands, or access conversation history beyond data included in the hook event. Use an agent-based hook when the evaluator must gather more context before deciding.
This example is available on GitHub: examples/01_standalone_sdk/57_prompt_hooks
examples/01_standalone_sdk/57_prompt_hooks/main.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Agent-based Hooks

Besides shell scripts, a hook can delegate its decision to an LLM-driven sub-agent by setting type="agent". The sub-agent receives the lifecycle event as JSON, reasons about it semantically, and replies with a decision payload:
This is useful when a syntactic blacklist is not enough — for example, a PreToolUse reviewer that recognises awk '{print}' /etc/passwd as reading a sensitive file even though no obvious keyword (cat, /etc/shadow) appears. Key fields on an agent HookDefinition:
  • name — a label for the hook; identifies it in logs, events, and its agent-hook:<name> metrics bucket.
  • system_prompt — the policy the reviewer agent follows.
  • tools — optional tools the reviewer may use (e.g. ["file_editor"] to inspect the workspace before deciding).
  • timeout / max_iterations — bound how long the reviewer runs.
The agent hook runs in an isolated sub-conversation (its own ephemeral state, no nested hooks), and its LLM spend is tracked under an agent-hook:<name> usage bucket that is merged back into the parent conversation’s metrics. If no LLM is available or the reviewer fails to produce a valid decision, the hook falls open (allows) so it never blocks the agent on an internal error.
This example is available on GitHub: examples/01_standalone_sdk/51_agent_hooks
examples/01_standalone_sdk/51_agent_hooks/main.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Next Steps