Lesson 1.5
Agent SDK Hooks and Lifecycle Events
How the Claude Agent SDK exposes the Claude Code harness as a library, and how hooks let your code observe and steer every stage of a run.
The Claude Agent SDK packages the Claude Code harness, with its built-in tools, agentic loop, context management, permissions, subagents and sessions, as a library for Python (claude-agent-sdk) and TypeScript (@anthropic-ai/claude-agent-sdk). You call a single entry point with a prompt and options, and the SDK drives the loop. Hooks are the mechanism for inserting your own logic at defined points of that loop. The exam distinguishes the SDK from the plain Anthropic API SDK and tests the hook event model.
Two SDKs, two jobs
The Anthropic API SDK (anthropic / @anthropic-ai/sdk) is a thin client for POST /v1/messages. You define every tool and own the loop, or use its tool runner helper. The Agent SDK is a complete agent: it ships Read, Write, Edit, Bash, Glob, Grep, web tools and MCP support, and it already knows how to compact context, spawn subagents and enforce permissions. Choose the Agent SDK when you want a coding or filesystem agent on your own infrastructure; choose the API SDK when you want full control over a loop with your own tools.
The entry point and its messages
from claude_agent_sdk import query, ClaudeAgentOptions
async for message in query(
prompt="Add input validation to the signup form",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Bash"],
permission_mode="acceptEdits",
max_turns=20,
cwd="/path/to/repo",
),
):
print(type(message).__name__)
The stream yields typed messages that map to the lifecycle of a run: a system init message first (carrying the session_id you need for resumption), then alternating assistant and user messages as the loop proceeds (tool calls and their results), optional partial stream events if enabled, and finally a result message with the outcome, turn count, duration and cost. Production code reads the result message for cost accounting and the init message to persist the session ID.
Two options frequently catch people out. settingSources controls whether the SDK loads filesystem settings such as CLAUDE.md and .claude/settings.json; by default it loads none, so an SDK agent does not automatically see your project instructions unless you ask for ["project"] or similar. And systemPrompt can be a plain string, replacing the harness prompt, or a preset reference that keeps the Claude Code system prompt and appends your text.
Hook events
Hooks are callbacks registered against named lifecycle events. The core set in both SDKs:
| Event | Fires when | Typical use |
|---|---|---|
PreToolUse | Before a tool executes | Block, modify input, require approval |
PostToolUse | After a tool returns | Log, validate output, add context |
UserPromptSubmit | A prompt enters the loop | Inject context, reject prompts |
Stop | The agent is about to finish | Enforce completion criteria |
SubagentStop | A subagent finishes | Audit delegated work |
PreCompact | Before context compaction | Save transcript, add instructions |
Session start and end and notification events exist in the TypeScript SDK; check the current documentation for Python support, which has lagged on events that require process-level setup.
Registering a hook
from claude_agent_sdk import HookMatcher
async def block_rm(input_data, tool_use_id, context):
cmd = input_data.get("tool_input", {}).get("command", "")
if "rm -rf" in cmd:
return {"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked"}}
return {}
options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[block_rm])]}
)
The matcher is a pattern on the tool name; omit it to match every tool. A hook returns an empty object to allow the action unchanged. For PreToolUse it can return permissionDecision (allow, deny, ask) with a reason, or updatedInput to rewrite the tool arguments before execution. For UserPromptSubmit and PostToolUse it can return additionalContext that the model sees. Any hook can set continue: false to halt the run entirely.
In TypeScript the shape is the same: hooks: { PreToolUse: [{ matcher: "Bash", hooks: [async (input, toolUseId, { signal }) => ({...})] }] }.
Hooks versus canUseTool
The SDK also offers a canUseTool callback invoked whenever a tool call needs permission under the current mode. It is the right place for an interactive approval UI because it is awaited per call and returns an allow or deny decision (optionally with modified input). Hooks are broader: they fire on events that have nothing to do with permissions and can run on every call regardless of mode. Use canUseTool for the human prompt, hooks for policy and observability.
SDK hooks versus Claude Code settings hooks
Claude Code's settings hooks are shell commands configured in JSON and communicate through stdin, stdout and exit codes. SDK hooks are in-process functions with the same event names and equivalent JSON return fields. If you pass settingSources, the SDK also runs the settings-file hooks, so both can be active at once.
Exam focus
- The Agent SDK is Claude Code as a library with built-in tools; the API SDK is a Messages API client with no built-in tools.
query()streams lifecycle messages:init(withsession_id), assistant and user turns, then a result with cost and turn count.- By default the SDK loads no filesystem settings;
settingSourcesmust be set forCLAUDE.mdand settings hooks to apply. - Hook events include
PreToolUse,PostToolUse,UserPromptSubmit,Stop,SubagentStopandPreCompact, each registered with an optional matcher. PreToolUsehooks returnpermissionDecisionorupdatedInput;canUseToolis the per-call approval callback.- SDK hooks are in-process callbacks; Claude Code settings hooks are shell commands using exit codes and stdin JSON.
Check yourself
5 questions
1.How are hooks defined when using the Agent SDK in Python or TypeScript?
2.A PreToolUse hook is meant to block writes to any .env file. It is registered with matcher 'Write|Edit', yet Claude creates a .env file through a Bash echo redirect and the hook never fires. Why?
3.What should a PreToolUse hook callback return to block a tool call and explain the reason to Claude?
4.Three PreToolUse hooks are registered for the same tool call: an audit logger returning allow, a validator returning ask, and a policy check returning deny. What is the outcome?
5.Which hook event should an architect use to archive the full transcript before the SDK summarises the conversation?
