llms.txt@05-Agent-SDK · git:20261011.c214a3a · 2026-10-11 · sha256 dd83a47e73a8d7db

llms.txt@05-Agent-SDK git:20261011.c214a3aF

Immutable. This exact content never changes and is served at /api/v1/blob/dd83a47e73a8d7db.

# Agent SDK

> Every page below, concatenated. Each section gives the page title, its upstream source and its path in this repository.

---

## How the agent loop works - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/agent-loop
Path: 05-Agent-SDK/agent-sdk-agent-loop.md

## On this page

- [The loop at a glance](#the-loop-at-a-glance)
- [Turns and messages](#turns-and-messages)
- [Message types](#message-types)
  - [Handle messages](#handle-messages)
- [Tool execution](#tool-execution)
  - [Built-in tools](#built-in-tools)
  - [Tool permissions](#tool-permissions)
  - [Parallel tool execution](#parallel-tool-execution)
- [Control how the loop runs](#control-how-the-loop-runs)
  - [Turns and budget](#turns-and-budget)
  - [Budget headroom](#budget-headroom)
  - [Effort level](#effort-level)
  - [Permission mode](#permission-mode)
  - [Model](#model)
- [The context window](#the-context-window)
  - [What consumes context](#what-consumes-context)
  - [Automatic compaction](#automatic-compaction)
  - [Keep context efficient](#keep-context-efficient)
- [Sessions and continuity](#sessions-and-continuity)
- [Handle the result](#handle-the-result)
- [Hooks](#hooks)
- [Put it all together](#put-it-all-together)
- [Next steps](#next-steps)

Core concepts

# How the agent loop works

Copy pageCopy page

Understand the message lifecycle, tool execution, context window, and architecture that power your SDK agents.

Copy pageCopy page

The Agent SDK lets you embed Claude Code’s autonomous agent loop in your own applications. The SDK is a standalone package that gives you programmatic control over tools, permissions, cost limits, and output. Both the TypeScript and Python SDKs bundle a native Claude Code binary, so most installs need no separate Claude Code install. See the [quickstart’s install note](agent-sdk-quickstart.md) for the installs that do. When you start an agent, the SDK runs the same [execution loop that powers Claude Code](../02-Claude-Code-CLI/how-claude-code-works.md#the-agentic-loop): Claude evaluates your prompt, calls tools to take action, receives the results, and repeats until the task is complete. This page explains what happens inside that loop so you can build, debug, and optimize your agents effectively.


[​](#the-loop-at-a-glance)

The loop at a glance

Every agent session follows the same cycle:

1.  **Receive prompt.** Claude receives your prompt, along with the system prompt, tool definitions, and conversation history. The SDK yields a [`SystemMessage`](#message-types) with subtype `"init"` containing session metadata.
2.  **Evaluate and respond.** Claude evaluates the current state and determines how to proceed. It may respond with text, request one or more tool calls, or both. The SDK yields one or more [`AssistantMessage`](#message-types) objects, one for each content block, such as a text block or a tool call request.
3.  **Execute tools.** The SDK runs each requested tool and collects the results. Each set of tool results feeds back to Claude for the next decision. You can use [hooks](agent-sdk-hooks.md) to intercept, modify, or block tool calls before they run.
4.  **Repeat.** Steps 2 and 3 repeat as a cycle. Each full cycle is one turn. Claude continues calling tools and processing results until it produces a response with no tool calls.
5.  **Return result.** The SDK yields a final [`AssistantMessage`](#message-types) with the text response (no tool calls), followed by a [`ResultMessage`](#message-types) with the final text, token usage, cost, and session ID.

A quick question (“what files are here?”) might take one or two turns of calling `Glob` and responding with the results. A complex task (“refactor the auth module and update the tests”) can chain dozens of tool calls across many turns, reading files, editing code, and running tests, with Claude adjusting its approach based on each result.


[​](#turns-and-messages)

Turns and messages

A turn is one round trip inside the loop: Claude produces output that includes tool calls, the SDK executes those tools, and the results feed back to Claude automatically. This happens without yielding control back to your code. Turns continue until Claude produces output with no tool calls, at which point the loop ends and the final result is delivered. Consider what a full session might look like for the prompt “Fix the failing tests in auth.ts”. First, the SDK sends your prompt to Claude and yields a [`SystemMessage`](#message-types) with the session metadata. Then the loop begins:

1.  **Turn 1:** Claude calls `Bash` to run `npm test`. The SDK yields an [`AssistantMessage`](#message-types) with the tool call, executes the command, then yields a [`UserMessage`](#message-types) with the output (three failures).
2.  **Turn 2:** Claude calls `Read` on `auth.ts` and `auth.test.ts`. The SDK yields an `AssistantMessage` for each call and returns the file contents.
3.  **Turn 3:** Claude calls `Edit` to fix `auth.ts`, then calls `Bash` to re-run `npm test`. All three tests pass. The SDK yields an `AssistantMessage` for each call.
4.  **Final turn:** Claude produces a text-only response with no tool calls: “Fixed the auth bug, all three tests pass now.” The SDK yields a final `AssistantMessage` with this text, then a [`ResultMessage`](#message-types) with the same text plus cost and usage.

That was four turns: three with tool calls, one final text-only response. You can cap the loop with `max_turns` / `maxTurns`, which counts tool-use turns only. For example, `max_turns=2` in the loop above would have stopped before the edit step. You can also use `max_budget_usd` / `maxBudgetUsd` to cap turns based on a spend threshold. Without limits, the loop runs until Claude finishes on its own, which is fine for well-scoped tasks but can run long on open-ended prompts (“improve this codebase”). Setting a budget is a good default for production agents. See [Turns and budget](#turns-and-budget) below for the option reference.


[​](#message-types)

Message types

As the loop runs, the SDK yields a stream of messages. Each message carries a type that tells you what stage of the loop it came from. The five core types are:

- **`SystemMessage`:** session lifecycle events. The `subtype` field distinguishes them:
  - `"init"`: session metadata for the run. When a `SessionStart` or `Setup` hook runs during session startup, its [hook lifecycle messages](agent-sdk-typescript.md#sdkhookstartedmessage) arrive before the `init` message
  - `"compact_boundary"`: fires after [compaction](#automatic-compaction)
  - `"informational"`: plain-text status banners from the loop
  - `"worker_shutting_down"`: the host is exiting or Remote Control disconnected

  In TypeScript, each subtype other than `"init"` is its own type in the [`SDKMessage` union](agent-sdk-typescript.md#sdkmessage) rather than a subtype of `SDKSystemMessage`.
- **`AssistantMessage`:** emitted for each content block in Claude’s responses, including the final text-only one. Each carries a single content block, such as text or a tool call, and the messages from one response share a message ID.
- **`UserMessage`:** emitted after each tool execution with the tool result content sent back to Claude. Also emitted for any user inputs you stream mid-loop.
- **`StreamEvent`:** only emitted when partial messages are enabled. Contains raw API streaming events (text deltas, tool input chunks). See [Stream responses](agent-sdk-streaming-output.md).
- **`ResultMessage`:** marks the end of the agent loop. Contains the final text result, token usage, cost, and session ID. Check the `subtype` field to determine whether the task succeeded or hit a limit. A small number of trailing system events, such as `prompt_suggestion`, can arrive after it, so iterate the stream to completion rather than breaking on the result. See [Handle the result](#handle-the-result).

These five types cover the full agent loop lifecycle. Both SDKs also yield observability events such as rate-limit status and task notifications that are not required to drive the loop. See the [Python message types reference](agent-sdk-python.md#message-types) and [TypeScript message types reference](agent-sdk-typescript.md#message-types) for the complete lists.


[​](#handle-messages)

Handle messages

Which messages you handle depends on what you’re building:

- **Final results only:** handle `ResultMessage` to get the output, cost, and whether the task succeeded or hit a limit.
- **Progress updates:** handle `AssistantMessage` to see what Claude is doing each turn, including which tools it called.
- **Live streaming:** enable partial messages (`include_partial_messages` in Python, `includePartialMessages` in TypeScript) to get `StreamEvent` messages in real time. See [Stream responses in real-time](agent-sdk-streaming-output.md).

How you check message types depends on the SDK:

- **Python:** check message types with `isinstance()` against classes imported from `claude_agent_sdk` (for example, `isinstance(message, ResultMessage)`).
- **TypeScript:** check the `type` string field (for example, `message.type === "result"`). `AssistantMessage` and `UserMessage` wrap the raw API message in a `.message` field, so content blocks are at `message.message.content`, not `message.content`.

Example: Check message types and handle results

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, AssistantMessage, ResultMessage, TextBlock, ToolUseBlock


async def main():
    try:
        async for message in query(prompt="Summarize this project"):
            if isinstance(message, AssistantMessage):
                # Each AssistantMessage carries one content block
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")
                    elif isinstance(block, ToolUseBlock):
                        print(f"Tool call: {block.name}")
            if isinstance(message, ResultMessage):
                if message.subtype == "success":
                    print(message.result)
                else:
                    print(f"Stopped: {message.subtype}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branches above have
        # already run; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type === "assistant") {
      // Each assistant message carries one content block
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(`Claude: ${block.text}`);
        } else if (block.type === "tool_use") {
          console.log(`Tool call: ${block.name}`);
        }
      }
    }
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log(message.result);
      } else {
        console.log(`Stopped: ${message.subtype}`);
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branches above have
  // already run; connection or process failures yield no result message.
  console.log(`Session ended with an error: ${error}`);
}
```


[​](#tool-execution)

Tool execution

Tools give your agent the ability to take action. Without tools, Claude can only respond with text. With tools, Claude can read files, run commands, search code, and interact with external services.


[​](#built-in-tools)

Built-in tools

The SDK includes the same tools that power Claude Code:

| Category            | Tools                                                           | What they do                                                                |
|:--------------------|:----------------------------------------------------------------|:----------------------------------------------------------------------------|
| **File operations** | `Read`, `Edit`, `Write`                                         | Read, modify, and create files                                              |
| **Search**          | `Glob`, `Grep`                                                  | Find files by pattern, search content with regex                            |
| **Execution**       | `Bash`                                                          | Run shell commands, scripts, git operations                                 |
| **Web**             | `WebSearch`, `WebFetch`                                         | Search the web, fetch and parse pages                                       |
| **Discovery**       | `ToolSearch`                                                    | Dynamically find and load tools on-demand instead of preloading all of them |
| **Orchestration**   | `Agent`, `Skill`, `AskUserQuestion`, `TaskCreate`, `TaskUpdate` | Spawn subagents, invoke skills, ask the user, track tasks                   |

On the [models that don’t get the task-tracking tools](agent-sdk-todo-tracking.md#model-availability), Claude Code provides `TaskCreate` and `TaskUpdate` only when you opt in. Beyond built-in tools, you can:

- **Connect external services** with [MCP servers](agent-sdk-mcp.md) (databases, browsers, APIs)
- **Define custom tools** with [custom tool handlers](agent-sdk-custom-tools.md)
- **Load project skills** via [setting sources](agent-sdk-claude-code-features.md) for reusable workflows


[​](#tool-permissions)

Tool permissions

Claude determines which tools to call based on the task, but you control whether those calls are allowed to execute. You can auto-approve specific tools, block others entirely, or require approval for everything. Three options work together to determine what runs:

- **`allowed_tools` / `allowedTools`** auto-approves listed tools. A read-only agent with `["Read", "Glob", "Grep"]` in its allowed tools list runs those tools without prompting. Tools not listed are still available, and calls to them that need approval fall through to the permission mode and `canUseTool`.
- **`disallowed_tools` / `disallowedTools`** blocks listed tools, regardless of other settings. See [Permissions](agent-sdk-permissions.md) for the order that rules are checked before a tool runs.
- **`permission_mode` / `permissionMode`** controls how much human oversight you want. The SDK evaluates the active mode together with your allow and deny rules in a fixed order, described in [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated). See [Permission mode](#permission-mode) for available modes.

You can also scope individual tools with rules like `"Bash(npm *)"` to allow only specific commands. See [Permissions](agent-sdk-permissions.md) for the full rule syntax. When a tool is denied, Claude receives a rejection message as the tool result and typically attempts a different approach or reports that it couldn’t proceed.


[​](#parallel-tool-execution)

Parallel tool execution

When Claude requests multiple tool calls in a single turn, both SDKs can run them concurrently or sequentially depending on the tool. Read-only tools (like `Read`, `Glob`, `Grep`, and MCP tools marked as read-only) can run concurrently. Tools that modify state (like `Edit`, `Write`, and `Bash`) run sequentially to avoid conflicts. Custom tools default to sequential execution. To enable parallel execution for a custom tool, set `readOnlyHint` in its annotations. Both the [TypeScript](agent-sdk-typescript.md#tool) and [Python](agent-sdk-python.md#tool) SDKs use this field name from the MCP SDK.


[​](#control-how-the-loop-runs)

Control how the loop runs

You can limit how many turns the loop takes, how much it costs, how deeply Claude reasons, and whether tools require approval before running. All of these are fields on [`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) (Python) / [`Options`](agent-sdk-typescript.md#options) (TypeScript).


[​](#turns-and-budget)

Turns and budget

| Option                                         | What it controls                        | Default  |
|:-----------------------------------------------|:----------------------------------------|:---------|
| Max turns (`max_turns` / `maxTurns`)           | Maximum tool-use round trips            | No limit |
| Max budget (`max_budget_usd` / `maxBudgetUsd`) | Estimated spend at which the loop stops | No limit |

When either limit is hit, the SDK returns a `ResultMessage` with a corresponding error subtype (`error_max_turns` or `error_max_budget_usd`). See [Handle the result](#handle-the-result) for how to check these subtypes and [`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) / [`Options`](agent-sdk-typescript.md#options) for syntax. The budget cap covers [subagents](agent-sdk-subagents.md): their spend counts toward the total. Once spend reaches the cap, spawning another subagent fails with `Budget limit reached`, and Claude Code stops any background subagents still running. The cap-enforcement behaviors require Claude Code v2.1.217 or later. With [streaming input](agent-sdk-streaming-vs-single-mode.md), a message that is still queued when a turn ends at the max-turns limit stays queued. Claude Code doesn’t add it to that turn’s last model call. It starts a new turn for the message, and the max-turns count starts over for that turn. The budget total keeps accumulating across messages, and once spend reaches `maxBudgetUsd`, later messages in the same conversation end with the `error_max_budget_usd` result. A [`/clear`](agent-sdk-cost-tracking.md) starts the budget over.


[​](#budget-headroom)

Budget headroom

Claude Code compares spend with the `max_budget_usd` / `maxBudgetUsd` cap after model responses arrive, because each response’s cost comes from the token usage the API returns with it. The response that reaches the cap still completes and counts toward [`total_cost_usd`](agent-sdk-cost-tracking.md#get-the-total-cost-of-a-query). Spend can therefore pass the cap by up to the cost of that one response, plus anything that subagents still running at that moment spend before they stop. Leave headroom for this when you set the cap.


[​](#effort-level)

Effort level

The `effort` option controls how much reasoning Claude applies. Lower effort levels use fewer tokens per turn and reduce cost. Not all models support the effort parameter. See [Effort](../04-API-Reference/Guides/build-with-claude-effort.md) for which models support it.

| Level      | Behavior                          | Good for                                                                                            |
|:-----------|:----------------------------------|:----------------------------------------------------------------------------------------------------|
| `"low"`    | Minimal reasoning, fast responses | File lookups, listing directories                                                                   |
| `"medium"` | Balanced reasoning                | Routine edits, standard tasks                                                                       |
| `"high"`   | Thorough analysis                 | Refactors, debugging                                                                                |
| `"xhigh"`  | Extended reasoning depth          | Coding and agentic tasks on the [models that support it](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) |
| `"max"`    | Maximum reasoning depth           | Multi-step problems requiring deep analysis                                                         |

If you don’t set `effort`, Claude Code resolves the effort level itself, in the order [Adjust effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) describes.

`effort` trades latency and token cost for reasoning depth within each response. [Extended thinking](../04-API-Reference/Guides/build-with-claude-extended-thinking.md) is a separate feature that produces `thinking` blocks in the output, and the `display` field on `ThinkingConfig` for [Python](agent-sdk-python.md#thinkingconfig) or [TypeScript](agent-sdk-typescript.md#thinkingconfig) controls whether you receive their text. They are independent: you can set `effort: "low"` with extended thinking enabled, or `effort: "max"` without it.

Use lower effort for agents doing simple, well-scoped tasks (like listing files or running a single grep) to reduce cost and latency. Set `effort` in the top-level `query()` options for the whole session, or per subagent with the `effort` field on [`AgentDefinition`](agent-sdk-subagents.md#agentdefinition-configuration) to override the session level.


[​](#permission-mode)

Permission mode

The permission mode option (`permission_mode` in Python, `permissionMode` in TypeScript) controls whether the agent asks for approval before using tools:

| Mode                  | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Use case                                                                                                                                      |
|:----------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------|
| `"default"`           | Tool calls that need approval and aren’t covered by allow rules trigger your `canUseTool` callback; no callback means deny                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Interactive applications with a custom approval callback                                                                                      |
| `"acceptEdits"`       | Auto-approves file edits and common filesystem commands (`mkdir`, `touch`, `mv`, `cp`, etc.); other Bash commands follow default rules                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | You trust Claude’s edits and want faster iteration, such as during prototyping or when working in an isolated directory                       |
| `"plan"`              | Claude explores and plans without editing your source files; file edits are never auto-approved and prompt through your `canUseTool` callback                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | You want Claude to propose changes without executing them, such as during code review or when you need to approve changes before they’re made |
| `"dontAsk"`           | Never prompts. Tools pre-approved by [permission rules](../02-Claude-Code-CLI/settings-reference.md#permission-settings) run, and so do calls that need no approval in `default` mode, such as file reads inside your working directories; every call that would otherwise prompt is denied. `AskUserQuestion`, connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools), and MCP tools marked [`requiresUserInteraction`](../06-MCP-Tools/General/mcp.md#require-approval-for-a-specific-tool) are denied even if you’ve allowed them                                                                                                                                                                                                                                         | You want a fixed, explicit tool surface for a headless agent and prefer a hard deny over silent reliance on `canUseTool` being absent         |
| `"auto"`              | Uses a model classifier to review actions such as shell commands and network requests, allowing or blocking each one it reviews. See [Auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) for availability and the decision order                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Autonomous agents that still want safety guardrails on tool use                                                                               |
| `"bypassPermissions"` | Runs all allowed tools without asking, except tools matched by an explicit [`ask` rule](../02-Claude-Code-CLI/settings-reference.md#permission-settings), connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools), and tools that require user interaction. The [cross-session messaging safeguards](../02-Claude-Code-CLI/permission-modes.md#skip-all-checks-with-bypasspermissions-mode) still apply. See [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) for the precedence order. In the TypeScript SDK, also requires `allowDangerouslySkipPermissions: true` in `options`. Can’t be used when running as root on Unix. Use only in isolated environments where the agent’s actions can’t affect systems you care about | CI, containers, or other isolated environments                                                                                                |

For interactive applications, use `"default"` with a tool approval callback to surface approval prompts. For autonomous agents on a dev machine, `"acceptEdits"` auto-approves file edits and common filesystem commands (`mkdir`, `touch`, `mv`, `cp`, etc.) while still gating other `Bash` commands behind allow rules. Reserve `"bypassPermissions"` for CI, containers, or other isolated environments. See [Permissions](agent-sdk-permissions.md) for full details.


[​](#model)

Model

Set the `model` option to choose which model runs the session. For more information, see [Choose a model](agent-sdk-configuration.md#choose-a-model).


[​](#the-context-window)

The context window

The context window is the total amount of information available to Claude during a session. It does not reset between turns within a session. Everything accumulates: the system prompt, tool definitions, conversation history, tool inputs, and tool outputs. Content that stays the same across turns (system prompt, tool definitions, CLAUDE.md) is automatically [prompt cached](../04-API-Reference/Guides/build-with-claude-prompt-caching.md), which reduces cost and latency for repeated prefixes. For how a custom system prompt or `append` text affects cache reuse across sessions, see [Modifying system prompts](agent-sdk-modifying-system-prompts.md#improve-prompt-caching-across-users-and-machines).


[​](#what-consumes-context)

What consumes context

Here’s how each component affects context in the SDK:

| Source                   | When it loads                                                                  | Impact                                                                                                                                                                                                                                                                                                                 |
|:-------------------------|:-------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **System prompt**        | Every request                                                                  | Small fixed cost, always present                                                                                                                                                                                                                                                                                       |
| **CLAUDE.md files**      | Session start, via [`settingSources`](agent-sdk-claude-code-features.md) | Full content in every request (but prompt-cached, so only the first request pays full cost)                                                                                                                                                                                                                            |
| **Tool definitions**     | Every request; MCP schemas deferred by default                                 | Built-in tool schemas load every request. [Tool search](agent-sdk-mcp.md#mcp-tool-search) defers MCP tool schemas by default, falling back to upfront loading on unsupported models and certain platforms. See [Configure tool search](agent-sdk-tool-search.md#configure-tool-search) for the full matrix |
| **Conversation history** | Accumulates over turns                                                         | Grows with each turn: prompts, responses, tool inputs, tool outputs                                                                                                                                                                                                                                                    |
| **Skill descriptions**   | Session start, via setting sources                                             | Short summaries; full content loads only when invoked                                                                                                                                                                                                                                                                  |

Large tool outputs consume significant context. Reading a big file or running a command with verbose output can use thousands of tokens in a single turn. Context accumulates across turns, so longer sessions with many tool calls build up significantly more context than short ones.


[​](#automatic-compaction)

Automatic compaction

When the context window approaches its limit, the SDK automatically compacts the conversation: it summarizes older history to free space, keeping your most recent exchanges and key decisions intact. The SDK emits a message with `type: "system"` and `subtype: "compact_boundary"` in the stream when this happens (in Python this is a `SystemMessage`; in TypeScript it is a separate `SDKCompactBoundaryMessage` type). Compaction replaces older messages with a summary, so specific instructions from early in the conversation may not be preserved. Persistent rules belong in CLAUDE.md (loaded via [`settingSources`](agent-sdk-claude-code-features.md)) rather than in the initial prompt, because CLAUDE.md content is re-injected on every request. You can customize compaction behavior in several ways:

- **Summarization instructions in CLAUDE.md:** The compactor reads your CLAUDE.md like any other context, so you can include a section telling it what to preserve when summarizing. The compactor matches on intent, so the section header is free-form.
- **`PreCompact` hook:** Run custom logic before compaction occurs, for example to archive the full transcript. The hook receives a `trigger` field (`manual` or `auto`). See [hooks](agent-sdk-hooks.md).
- **Manual compaction:** Send `/compact` as a prompt string to trigger compaction on demand. Commands sent this way are ordinary SDK inputs. See [dispatch commands by name](agent-sdk-skills.md#dispatch-commands-by-name).

Example: Summarization instructions in CLAUDE.md

Add a section to your project’s CLAUDE.md telling the compactor what to preserve. The header name isn’t special; use any clear label.

CLAUDE.md

```python
# Summary instructions

When summarizing this conversation, always preserve:
- The current task objective and acceptance criteria
- File paths that have been read or modified
- Test results and error messages
- Decisions made and the reasoning behind them
```


[​](#keep-context-efficient)

Keep context efficient

A few strategies for long-running agents:

- **Use subagents for subtasks.** Each subagent starts with a fresh conversation (no prior message history, though it does load its own system prompt and project-level context like CLAUDE.md). It does not see the parent’s turns, and only its final response returns to the parent. The main agent’s context grows by that summary, not by the full subtask transcript. See [What subagents inherit](agent-sdk-subagents.md#what-subagents-inherit) for details.
- **Be selective with tools.** Every tool definition takes context space. Use the `tools` field on [`AgentDefinition`](agent-sdk-subagents.md#agentdefinition-configuration) to scope subagents to the minimum set they need.
- **Watch MCP server costs.** [MCP tool search](agent-sdk-mcp.md#mcp-tool-search) defers MCP tool schemas by default and loads them on demand. When tool search is off or has fallen back to upfront loading, each MCP server adds all its tool schemas to every request, so a few servers with many tools can consume significant context before the agent does any work. See [Configure tool search](agent-sdk-tool-search.md#configure-tool-search) for the configurations where the fallback applies.
- **Use lower effort for routine tasks.** Set [effort](#effort-level) to `"low"` for agents that only need to read files or list directories. This reduces token usage and cost.

For a detailed breakdown of per-feature context costs, see [Understand context costs](../02-Claude-Code-CLI/features-overview.md#understand-context-costs).


[​](#sessions-and-continuity)

Sessions and continuity

Each interaction with the SDK creates or continues a session. Capture the session ID from `ResultMessage.session_id` (available in both SDKs) to resume later. The TypeScript SDK also exposes it as a direct field on the init `SystemMessage`; in Python it’s nested in `SystemMessage.data`. When you resume, the full context from previous turns is restored: files that were read, analysis that was performed, and actions that were taken. You can also fork a session to branch into a different approach without modifying the original. See [Session management](agent-sdk-sessions.md) for the full guide on resume, continue, and fork patterns. To resume sessions across stateless containers or serverless hosts, pass a [`session_store` / `sessionStore` adapter](agent-sdk-session-storage.md) so the SDK mirrors transcripts to your own backend and another host can resume them. The Claude Code subprocess still writes to local disk first. See [Dual-write architecture](agent-sdk-session-storage.md#dual-write-architecture) for which copy outlives a fresh session versus a run resumed from the store, and how to keep the local copy ephemeral.

In Python, `ClaudeSDKClient` handles session IDs automatically across multiple calls. See the [Python SDK reference](agent-sdk-python.md#choosing-between-query-and-claudesdkclient) for details.


[​](#handle-the-result)

Handle the result

When the loop ends, the `ResultMessage` tells you what happened and gives you the output. The `subtype` field (available in both SDKs) is the primary way to check termination state.

| Result subtype                        | What happened                                                                                                                                                                           | `result` field available? |
|:--------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------:|
| `success`                             | Claude finished the task normally                                                                                                                                                       |            Yes            |
| `error_max_turns`                     | Hit the `maxTurns` limit before finishing                                                                                                                                               |            No             |
| `error_max_budget_usd`                | Hit the `maxBudgetUsd` limit before finishing                                                                                                                                           |            No             |
| `error_during_execution`              | An error interrupted the loop (for example, a cancelled request)                                                                                                                        |            No             |
| `error_max_structured_output_retries` | No valid structured output was produced within the configured retry limit: every attempt failed validation, or a model fallback retracted the completed output with no successful retry |            No             |

The `result` field holds the final text output and is only present on the `success` variant, so always check the subtype before reading it. All result subtypes carry `total_cost_usd`, `usage`, `num_turns`, and `session_id` so you can track cost and resume even after errors. Guard for these cases:

- After a session crash, the final result is an `error_during_execution` whose cost fields may be zeroed and whose `stop_reason` is `null`, and the process exits after emitting it. See [Recover totals after a session crash](agent-sdk-cost-tracking.md#recover-totals-after-a-session-crash).
- In Python, `total_cost_usd`, `usage`, and `model_usage` are typed as optional, so check that they aren’t `None` before you read them.

The `usage` field covers only the main agent loop. Use `modelUsage`, or `model_usage` in Python, for whole-tree token and cost accounting. See [Tracking costs and usage](agent-sdk-cost-tracking.md) for details on interpreting the `usage` fields.

When a query ends on an error result:

- A single-shot `query()` call yields the final result message, then raises an error that includes the failure text, such as `Reached maximum number of turns`. The raise is intentional. Wrap the loop in a try block if your code needs to continue past it. The underlying Claude Code process also exits with a nonzero code.
- A streaming input session stays alive, and you can keep sending messages, except after a session crash, which emits a final `error_during_execution` result and exits the process.

The result also includes a `stop_reason` field (`string | null` in TypeScript, `str | None` in Python) indicating why the model stopped generating on its final turn. Common values are `end_turn` (model finished normally), `max_tokens` (hit the output token limit), and `refusal` (the model declined the request). On error results that the loop produced, `stop_reason` carries the value from the last assistant response before the loop ended; the result Claude Code synthesizes after a session crash carries `null`. To detect refusals, check `stop_reason === "refusal"` (TypeScript) or `stop_reason == "refusal"` (Python). See [`SDKResultMessage`](agent-sdk-typescript.md#sdkresultmessage) (TypeScript) or [`ResultMessage`](agent-sdk-python.md#resultmessage) (Python) for the full type.


[​](#hooks)

Hooks

[Hooks](agent-sdk-hooks.md) are callbacks that fire at specific points in the loop: before a tool runs, after it returns, when the agent finishes, and so on. Some commonly used hooks are:

| Hook                             | When it fires                       | Common uses                                |
|:---------------------------------|:------------------------------------|:-------------------------------------------|
| `PreToolUse`                     | Before a tool executes              | Validate inputs, block dangerous commands  |
| `PostToolUse`                    | After a tool returns                | Audit outputs, trigger side effects        |
| `UserPromptSubmit`               | When a prompt is sent               | Inject additional context into prompts     |
| `Stop`                           | When the agent finishes             | Validate the result, save session state    |
| `SubagentStart` / `SubagentStop` | When a subagent spawns or completes | Track and aggregate parallel task results  |
| `PreCompact`                     | Before context compaction           | Archive full transcript before summarizing |

Hooks run in your application process, not inside the agent’s context window, so they don’t consume context. Hooks can also short-circuit the loop: a `PreToolUse` hook that rejects a tool call prevents it from executing, and Claude receives the rejection message instead. Both SDKs support all the events above. The TypeScript SDK includes additional events that Python does not yet support. See [Control execution with hooks](agent-sdk-hooks.md) for the complete event list, per-SDK availability, and the full callback API.


[​](#put-it-all-together)

Put it all together

This example combines the key concepts from this page into a single agent that fixes failing tests. It configures the agent with allowed tools (auto-approved so the agent runs autonomously), project settings, and safety limits on turns and reasoning effort. As the loop runs, it captures the session ID for potential resumption, handles the final result, and prints the total cost. Because a single-shot `query()` call raises after yielding an error result, the loop is wrapped in a try block so the script exits cleanly when a limit is hit.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def run_agent():
    session_id = None

    try:
        async for message in query(
            prompt="Find and fix the bug causing test failures in the auth module",
            options=ClaudeAgentOptions(
                allowed_tools=[
                    "Read",
                    "Edit",
                    "Bash",
                    "Glob",
                    "Grep",
                ],  # Listing tools here auto-approves them (no prompting)
                setting_sources=[
                    "project"
                ],  # Load CLAUDE.md, skills, hooks from current directory
                max_turns=30,  # Prevent runaway sessions
                effort="high",  # Thorough reasoning for complex debugging
            ),
        ):
            # Handle the final result
            if isinstance(message, ResultMessage):
                session_id = message.session_id  # Save for potential resumption

                if message.subtype == "success":
                    print(f"Done: {message.result}")
                elif message.subtype == "error_max_turns":
                    # Agent ran out of turns. Resume with a higher limit.
                    print(f"Hit turn limit. Resume session {session_id} to continue.")
                elif message.subtype == "error_max_budget_usd":
                    print("Hit budget limit.")
                else:
                    print(f"Stopped: {message.subtype}")
                if message.total_cost_usd is not None:
                    print(f"Cost: ${message.total_cost_usd:.4f}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branches above have
        # already run; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(run_agent())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

let sessionId: string | undefined;

try {
  for await (const message of query({
    prompt: "Find and fix the bug causing test failures in the auth module",
    options: {
      allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Listing tools here auto-approves them (no prompting)
      settingSources: ["project"], // Load CLAUDE.md, skills, hooks from current directory
      maxTurns: 30, // Prevent runaway sessions
      effort: "high" // Thorough reasoning for complex debugging
    }
  })) {
    // Save the session ID to resume later if needed
    if (message.type === "system" && message.subtype === "init") {
      sessionId = message.session_id;
    }

    // Handle the final result
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log(`Done: ${message.result}`);
      } else if (message.subtype === "error_max_turns") {
        // Agent ran out of turns. Resume with a higher limit.
        console.log(`Hit turn limit. Resume session ${sessionId} to continue.`);
      } else if (message.subtype === "error_max_budget_usd") {
        console.log("Hit budget limit.");
      } else {
        console.log(`Stopped: ${message.subtype}`);
      }
      console.log(`Cost: $${message.total_cost_usd.toFixed(4)}`);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branches above have
  // already run; connection or process failures yield no result message.
  console.log(`Session ended with an error: ${error}`);
}
```

When the agent finishes successfully, the example prints a `Done:` line with the agent’s summary of the fix, then a line like `Cost: $0.0312`.


[​](#next-steps)

Next steps

Now that you understand the loop, here’s where to go depending on what you’re building:

- **Haven’t run an agent yet?** Start with the [quickstart](agent-sdk-quickstart.md) to get the SDK installed and see a full example running end to end.
- **Ready to hook into your project?** [Load CLAUDE.md, skills, and filesystem hooks](agent-sdk-claude-code-features.md) so the agent follows your project conventions automatically.
- **Building an interactive UI?** Enable [streaming](agent-sdk-streaming-output.md) to show live text and tool calls as the loop runs.
- **Need tighter control over what the agent can do?** Lock down tool access with [permissions](agent-sdk-permissions.md), and use [hooks](agent-sdk-hooks.md) to audit, block, or transform tool calls before they execute.
- **Running long or expensive tasks?** Offload isolated work to [subagents](agent-sdk-subagents.md) to keep your main context lean.
- **Deploying as a service?** See [Hosting the Agent SDK](agent-sdk-hosting.md) for container and serverless guidance, and [Session storage](agent-sdk-session-storage.md) to persist sessions to your own backend.

For the broader conceptual picture of the agentic loop (not SDK-specific), see [How Claude Code works](../02-Claude-Code-CLI/how-claude-code-works.md). For a practical guide to designing loops in Claude Code, from turn-based to goal-based and proactive loops, see [Loop engineering: getting started with loops](https://claude.com/blog/getting-started-with-loops) on the blog.

---

## Use Claude Code features in the SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/claude-code-features
Path: 05-Agent-SDK/agent-sdk-claude-code-features.md

## On this page

- [Control filesystem settings with settingSources](#control-filesystem-settings-with-settingsources)
  - [What settingSources does not control](#what-settingsources-does-not-control)
- [Project instructions (CLAUDE.md and rules)](#project-instructions-claude-md-and-rules)
  - [CLAUDE.md load locations](#claude-md-load-locations)
- [Skills](#skills)
- [Hooks](#hooks)
  - [When to use which hook type](#when-to-use-which-hook-type)
- [Choose the right feature](#choose-the-right-feature)
- [Related resources](#related-resources)

Core concepts

# Use Claude Code features in the SDK

Copy pageCopy page

Load project instructions, skills, hooks, and other Claude Code features into your SDK agents.

Copy pageCopy page

The Agent SDK is built on the same foundation as Claude Code, which means your SDK agents have access to the same filesystem-based features: project instructions (`CLAUDE.md` and rules), skills, hooks, and more. When you omit `settingSources`, `query()` reads the same filesystem settings as the Claude Code CLI: user, project, and local settings, CLAUDE.md files, and `.claude/` skills, agents, and commands. To run without these, pass `settingSources: []`, which limits the agent to what you configure programmatically. Managed policy settings and the global `~/.claude.json` config are read regardless of this option. For more information, see [What settingSources does not control](#what-settingsources-does-not-control).


[​](#control-filesystem-settings-with-settingsources)

Control filesystem settings with settingSources

The setting sources option ([`setting_sources`](agent-sdk-python.md#claudeagentoptions) in Python, [`settingSources`](agent-sdk-typescript.md#settingsource) in TypeScript) controls which filesystem-based settings the SDK loads. Pass an explicit list to opt in to specific sources, or pass an empty array to disable user, project, and local settings. This example loads both user-level and project-level settings by setting `settingSources` to `["user", "project"]`:

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage
import asyncio


async def main():
    async for message in query(
        prompt="Help me refactor the auth module",
        options=ClaudeAgentOptions(
            # "user" loads from ~/.claude/, "project" loads from ./.claude/ in cwd.
            # Together they give the agent access to CLAUDE.md, skills, hooks, and
            # permissions from both locations.
            setting_sources=["user", "project"],
            allowed_tools=["Read", "Edit", "Bash"],
        ),
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "text"):
                    print(block.text)
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(f"\nResult: {message.result}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Help me refactor the auth module",
  options: {
    // "user" loads from ~/.claude/, "project" loads from ./.claude/ in cwd.
    // Together they give the agent access to CLAUDE.md, skills, hooks, and
    // permissions from both locations.
    settingSources: ["user", "project"],
    allowedTools: ["Read", "Edit", "Bash"]
  }
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "text") console.log(block.text);
    }
  }
  if (message.type === "result" && message.subtype === "success") {
    console.log(`\nResult: ${message.result}`);
  }
}
```

When this runs, the assistant’s response prints to stdout, followed by a final result line once the run completes. Each source loads settings from a specific location, where `<cwd>` is the working directory you pass via the `cwd` option, or the process’s current directory if unset. For the full type definition, see [`SettingSource`](agent-sdk-typescript.md#settingsource) (TypeScript) or [`SettingSource`](agent-sdk-python.md#settingsource) (Python).

| Source      | What it loads                                                                                                          | Location                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|:------------|:-----------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `"project"` | Project `settings.json` and hooks; project CLAUDE.md and `.claude/rules/*.md`; project skills, commands, and subagents | `<cwd>/.claude/` for `settings.json` and hooks; `<cwd>` and every parent directory for CLAUDE.md and rules; `<cwd>` and every parent directory up to the repository root for skills, commands, and subagents, plus the `.claude/skills/`, `.claude/commands/`, and `.claude/agents/` folders of each directory you pass through the `additionalDirectories` or `add_dirs` option, which the SDK passes to Claude Code as [`--add-dir`](../02-Claude-Code-CLI/permissions.md#additional-directories-grant-file-access-not-configuration) |
| `"user"`    | User `settings.json`; user CLAUDE.md and `~/.claude/rules/*.md`; user skills, commands, and subagents                  | `~/.claude/` for `settings.json`, CLAUDE.md, and rules; `~/.claude/skills/`, `~/.claude/commands/`, and `~/.claude/agents/` for skills, commands, and subagents                                                                                                                                                                                                                                                                                                                                                         |
| `"local"`   | CLAUDE.local.md, `.claude/settings.local.json`                                                                         | `<cwd>/.claude/` for `settings.local.json`; `<cwd>` and every parent directory for CLAUDE.local.md                                                                                                                                                                                                                                                                                                                                                                                                                      |

Omitting `settingSources` is equivalent to `["user", "project", "local"]`. The `cwd` option determines where the SDK looks for project-level inputs. Project `settings.json` and hooks load only from `<cwd>/.claude/` with no parent-directory fallback.


[​](#what-settingsources-does-not-control)

What settingSources does not control

`settingSources` covers user, project, and local settings. A few inputs are read regardless of its value:

| Input                                                                                                                                | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | To disable                                                                                                                                                                                                                                                      |
|:-------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Managed policy settings                                                                                                              | Endpoint-managed policy, such as an MDM plist, registry policy, or managed settings file, loads from the host. [Server-managed settings](../13-Enterprise-Admin/server-managed-settings.md) are fetched on an [eligible configuration](../13-Enterprise-Admin/server-managed-settings.md#platform-availability) when the session authenticates with a qualifying credential, such as an organization OAuth login, a directly configured API key, or a `user_oauth` [Anthropic profile](../02-Claude-Code-CLI/authentication.md#anthropic-profiles-and-federation-credentials) | Endpoint policy: remove the managed settings file, plist, or registry policy from the host. Server-managed settings: an [Owner](../13-Enterprise-Admin/server-managed-settings.md#access-control) in your Claude organization controls them; you can’t disable them from the SDK |
| `~/.claude.json` global config                                                                                                       | Always read                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Relocate with `CLAUDE_CONFIG_DIR` in `env`                                                                                                                                                                                                                      |
| Auto memory at `~/.claude/projects/<project>/memory/`                                                                                | Loaded into the system prompt at session start. The agent writes new memories there with the standard `Write` and `Edit` tools rather than a dedicated memory tool, so those tools must be enabled for the agent to save memories                                                                                                                                                                                                                                                                                           | Set `autoMemoryEnabled: false` in settings, or `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` in `env`                                                                                                                                                                     |
| [claude.ai MCP connectors](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai)                                                              | Loaded when the session authenticates with your claude.ai login. Not loaded when `CLAUDE_CODE_OAUTH_TOKEN` holds a token from [`claude setup-token`](../02-Claude-Code-CLI/authentication.md#generate-a-long-lived-token), which can only make model requests. Passing `mcpServers: {}` does not suppress the connectors                                                                                                                                                                                                                    | Set `strictMcpConfig: true`, [`disableClaudeAiConnectors: true`](../06-MCP-Tools/General/mcp.md#disable-claude-ai-connectors) in settings, or `ENABLE_CLAUDEAI_MCP_SERVERS=false` in `env`                                                                                        |
| [`sandbox.credentials`](../02-Claude-Code-CLI/sandboxing.md#protect-credentials) `deny` entries and file `mask` entries in `~/.claude/settings.json` | When the [command sandbox](../02-Claude-Code-CLI/sandboxing.md) runs, Claude Code applies the `deny` entries and keeps the `credentials.files` `mask` entries as restrictions even when `settingSources` excludes user settings. Claude Code uses these entries only to narrow what sandboxed commands can access                                                                                                                                                                                                                           | Remove the entries from `~/.claude/settings.json`                                                                                                                                                                                                               |

Do not rely on default `query()` options for multi-tenant isolation. Because the inputs above are read regardless of `settingSources`, an SDK process can pick up host-level configuration and per-directory memory. For multi-tenant deployments, run each tenant in its own filesystem and set `settingSources: []` plus `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` in `env`. [Server-managed settings](../13-Enterprise-Admin/server-managed-settings.md) are fetched when the process authenticates with an organization credential; filesystem isolation does not remove them. See [Secure deployment](agent-sdk-secure-deployment.md).


[​](#project-instructions-claude-md-and-rules)

Project instructions (CLAUDE.md and rules)

`CLAUDE.md` files and `.claude/rules/*.md` files give your agent persistent context about your project: coding conventions, build commands, architecture decisions, and instructions. When `settingSources` includes `"project"`, as in the [`settingSources` example](#control-filesystem-settings-with-settingsources), the SDK loads these files into context at session start. The agent then follows your project conventions without you repeating them in every prompt.


[​](#claude-md-load-locations)

CLAUDE.md load locations

| Level                 | Location                                                                      | When loaded                                                                                         |
|:----------------------|:------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------|
| Project (root)        | `<cwd>/CLAUDE.md` or `<cwd>/.claude/CLAUDE.md`                                | `settingSources` includes `"project"`                                                               |
| Project rules         | `<cwd>/.claude/rules/*.md` and `.claude/rules/*.md` in every parent directory | `settingSources` includes `"project"`                                                               |
| Project (parent dirs) | `CLAUDE.md` files in directories above `cwd`                                  | `settingSources` includes `"project"`, loaded at session start                                      |
| Project (child dirs)  | `CLAUDE.md` files in subdirectories of `cwd`                                  | `settingSources` includes `"project"`, loaded [on demand](../02-Claude-Code-CLI/memory.md#how-claude-md-files-load) |
| Local                 | `<cwd>/CLAUDE.local.md` and `CLAUDE.local.md` in every parent directory       | `settingSources` includes `"local"`                                                                 |
| User                  | `~/.claude/CLAUDE.md`                                                         | `settingSources` includes `"user"`                                                                  |
| User rules            | `~/.claude/rules/*.md`                                                        | `settingSources` includes `"user"`                                                                  |

All levels are additive: if both project and user CLAUDE.md files exist, the agent sees both. There is no hard precedence rule between levels; if instructions conflict, the outcome depends on how Claude interprets them. Write non-conflicting rules, or state precedence explicitly in the more specific file (“These project instructions override any conflicting user-level defaults”).

You can also inject context directly via `systemPrompt` without using CLAUDE.md files. See [Modify system prompts](agent-sdk-modifying-system-prompts.md). Use CLAUDE.md when you want the same context shared between interactive Claude Code sessions and your SDK agents.

For how to structure and organize CLAUDE.md content, see [Manage Claude’s memory](../02-Claude-Code-CLI/memory.md).


[​](#skills)

Skills

Skills are markdown files that give your agent specialized knowledge and invocable workflows. Unlike `CLAUDE.md` (which loads every session), skills load on demand. The agent receives skill descriptions at startup and loads the full content when relevant. Skills are discovered from the filesystem through `settingSources`. When the `skills` option on `query()` is omitted, discovered user and project skills are enabled and the Skill tool is available, matching CLI behavior. To control which skills are enabled, pass `skills` as `"all"`, a list of skill names, or `[]` to disable all. When `skills` is set, the SDK adds the Skill tool to `allowedTools` automatically. If you also pass an explicit `tools` list, include `"Skill"` in that list so Claude can invoke skills.

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
import asyncio


# Skills in .claude/skills/ are discovered automatically
# when settingSources includes "project"
async def main():
    async for message in query(
        prompt="Review this PR using our code review checklist",
        options=ClaudeAgentOptions(
            setting_sources=["user", "project"],
            skills="all",
            allowed_tools=["Read", "Grep", "Glob"],
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Skills in .claude/skills/ are discovered automatically
// when settingSources includes "project"
for await (const message of query({
  prompt: "Review this PR using our code review checklist",
  options: {
    settingSources: ["user", "project"],
    skills: "all",
    allowedTools: ["Read", "Grep", "Glob"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

Skills must be created as filesystem artifacts (`.claude/skills/<name>/SKILL.md`). The SDK does not have a programmatic API for registering skills. See [Agent Skills in the SDK](agent-sdk-skills.md) for full details.


[​](#hooks)

Hooks

The SDK supports two ways to define hooks, and they run side by side:

- **Filesystem hooks:** shell commands defined in `settings.json`, loaded when `settingSources` includes the relevant source. These are the same hooks you’d configure for [interactive Claude Code sessions](../07-Hooks/hooks-guide.md).
- **Programmatic hooks:** callback functions passed directly to `query()`. These run in your application process and can return structured decisions. See [Control execution with hooks](agent-sdk-hooks.md).

Hook callbacks receive the tool input and return a decision dict. Returning `{}` means allow the tool to proceed. To block execution, return a `hookSpecificOutput` object with `permissionDecision: "deny"` and a `permissionDecisionReason`. The reason is sent to Claude as the tool result. See the [hooks guide](agent-sdk-hooks.md) for the full callback signature and return types.

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, ResultMessage
import asyncio


# PreToolUse hook callback. Positional args:
#   input_data: HookInput dict with tool_name, tool_input, hook_event_name
#   tool_use_id: str | None, the ID of the tool call being intercepted
#   context: HookContext, reserved for future abort-signal support
async def audit_bash(input_data, tool_use_id, context):
    command = input_data.get("tool_input", {}).get("command", "")
    if "rm -rf" in command:
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": "Destructive command blocked",
            }
        }
    return {}  # Empty dict: allow the tool to proceed


# Filesystem hooks from .claude/settings.json run automatically
# when settingSources loads them. You can also add programmatic hooks:
async def main():
    async for message in query(
        prompt="Refactor the auth module",
        options=ClaudeAgentOptions(
            setting_sources=["project"],  # Loads hooks from .claude/settings.json
            hooks={
                "PreToolUse": [
                    HookMatcher(matcher="Bash", hooks=[audit_bash]),
                ]
            },
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query, type HookInput, type HookJSONOutput } from "@anthropic-ai/claude-agent-sdk";

// PreToolUse hook callback. HookInput is a discriminated union on
// hook_event_name, so narrowing on it gives TypeScript the right
// tool_input shape for this event.
const auditBash = async (input: HookInput): Promise<HookJSONOutput> => {
  if (input.hook_event_name !== "PreToolUse") return {};
  const toolInput = input.tool_input as { command?: string };
  if (toolInput.command?.includes("rm -rf")) {
    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: "Destructive command blocked",
      },
    };
  }
  return {}; // Empty object: allow the tool to proceed
};

// Filesystem hooks from .claude/settings.json run automatically
// when settingSources loads them. You can also add programmatic hooks:
for await (const message of query({
  prompt: "Refactor the auth module",
  options: {
    settingSources: ["project"], // Loads hooks from .claude/settings.json
    hooks: {
      PreToolUse: [{ matcher: "Bash", hooks: [auditBash] }]
    }
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```


[​](#when-to-use-which-hook-type)

When to use which hook type

| Hook type                                 | Best for                                                                                                                                                                                                                                                                                                     |
|:------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Filesystem** (`settings.json`)          | Sharing hooks between CLI and SDK sessions. Supports `"command"` (shell scripts), `"http"` (POST to an endpoint), `"mcp_tool"` (call a connected MCP server’s tool), `"prompt"` (LLM evaluates a prompt), and `"agent"` (spawns a verifier agent). These fire in the main agent and any subagents it spawns. |
| **Programmatic** (callbacks in `query()`) | Application-specific logic, structured decisions, and in-process integration. These also fire inside subagents. The hook input, the callback’s first argument, carries `agent_id` and `agent_type` fields that identify which agent fired the hook.                                                          |

The TypeScript SDK supports additional hook events beyond Python, including `SessionStart`, `SessionEnd`, `TeammateIdle`, and `TaskCompleted`. See the [hooks guide](agent-sdk-hooks.md) for the full event compatibility table.

For full details on programmatic hooks, see [Control execution with hooks](agent-sdk-hooks.md). For filesystem hook syntax, see [Hooks](../07-Hooks/hooks.md).


[​](#choose-the-right-feature)

Choose the right feature

The Agent SDK gives you access to several ways to extend your agent’s behavior. If you’re unsure which to use, this table maps common goals to the right approach.

| What you want to do                                                                               | Use                                                | SDK surface                                                                                                                                                    |
|:--------------------------------------------------------------------------------------------------|:---------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Set project conventions your agent always follows                                                 | [CLAUDE.md](../02-Claude-Code-CLI/memory.md)                       | `settingSources: ["project"]` loads it automatically                                                                                                           |
| Give the agent reference material it loads when relevant                                          | [Skills](agent-sdk-skills.md)                | `settingSources` + `skills` option                                                                                                                             |
| Run a reusable workflow (deploy, review, release)                                                 | [User-invocable skills](agent-sdk-skills.md) | `settingSources` + `skills` option                                                                                                                             |
| Delegate an isolated subtask to a fresh context (research, review)                                | [Subagents](agent-sdk-subagents.md)          | `agents` parameter + `allowedTools: ["Agent"]`                                                                                                                 |
| Coordinate multiple Claude Code instances with shared task lists and direct inter-agent messaging | [Agent teams](../09-Agents-Patterns/agent-teams.md)                | Not directly configured via SDK options. Agent teams are a CLI feature where one session acts as the team lead, coordinating work across independent teammates |
| Run deterministic logic on tool calls (audit, block, transform)                                   | [Hooks](agent-sdk-hooks.md)                  | `hooks` parameter with callbacks, or shell scripts loaded via `settingSources`                                                                                 |
| Give Claude structured tool access to an external service                                         | [MCP](agent-sdk-mcp.md)                      | `mcpServers` parameter                                                                                                                                         |

Every feature you enable adds to your agent’s context window. For per-feature costs and how these features layer together, see [Extend Claude Code](../02-Claude-Code-CLI/features-overview.md#understand-context-costs).


[​](#related-resources)

Related resources

- [Extend Claude Code](../02-Claude-Code-CLI/features-overview.md): Conceptual overview of all extension features, with comparison tables and context cost analysis
- [Skills in the SDK](agent-sdk-skills.md): Full guide to using skills programmatically
- [Subagents](agent-sdk-subagents.md): Define and invoke subagents for isolated subtasks
- [Hooks](agent-sdk-hooks.md): Intercept and control agent behavior at key execution points
- [Permissions](agent-sdk-permissions.md): Control tool access with modes, rules, and callbacks
- [System prompts](agent-sdk-modifying-system-prompts.md): Inject context without CLAUDE.md files

---

## Configure your agent - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/configuration
Path: 05-Agent-SDK/agent-sdk-configuration.md

## On this page

- [Pass options to a session](#pass-options-to-a-session)
- [Load settings files](#load-settings-files)
- [Choose a model](#choose-a-model)
- [Set environment variables](#set-environment-variables)
- [Set the working directory](#set-the-working-directory)
- [Limit turns and spend](#limit-turns-and-spend)
- [Change configuration mid-session](#change-configuration-mid-session)
- [Configure specific features](#configure-specific-features)
- [Next steps](#next-steps)

Build agents

# Configure your agent

Copy pageCopy page

Configure Agent SDK sessions: compose the options object, set the model, environment, and limits, and find each feature option’s page.

Copy pageCopy page

An Agent SDK session reads configuration from settings files, environment variables, and the `options` object you pass when you start it. This page shows how to compose the `options` object and what settings files and environment variables control. For every option’s type and default, see the [`Options`](agent-sdk-typescript.md#options) (TypeScript) and [`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) (Python) references.


[​](#pass-options-to-a-session)

Pass options to a session

Every `query()` call accepts an options object: `Options` in TypeScript, `ClaudeAgentOptions` in Python. Each field is optional, and a session started with no options runs with the SDK’s defaults. The example below configures a read-only session that summarizes a project’s open TODOs. Pairs read as TypeScript / Python where the spellings differ:

- **`model`**: picks the model
- **`allowedTools` / `allowed_tools`**: pre-approves a read-only tool list
- **`maxTurns` / `max_turns`**: caps the turn count
- **`cwd`**: sets the working directory

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Summarize the open TODOs in this repo",
  options: {
    model: "claude-sonnet-5",
    allowedTools: ["Read", "Glob", "Grep"],
    maxTurns: 8,
    cwd: "/path/to/repo",
  },
})) {
  if (message.type === "result" && message.subtype === "success" && !message.is_error) {
    console.log(message.result);
  }
}
```

```python
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query

async def main():
    options = ClaudeAgentOptions(
        model="claude-sonnet-5",
        allowed_tools=["Read", "Glob", "Grep"],
        max_turns=8,
        cwd="/path/to/repo",
    )

    async for message in query(
        prompt="Summarize the open TODOs in this repo",
        options=options,
    ):
        if isinstance(message, ResultMessage) and not message.is_error:
            print(message.result)

asyncio.run(main())
```

Point `cwd` at one of your own projects and run the example. The summary of that project’s open TODOs prints when the result message arrives. `allowedTools` (TypeScript) or `allowed_tools` (Python) pre-approves the listed tools, so calls to them run without stopping for approval. Tools outside the list stay available. When Claude calls an unlisted tool, the permission mode decides whether the call runs. For more information, see [Allow and deny rules](agent-sdk-permissions.md#allow-and-deny-rules).


[​](#load-settings-files)

Load settings files

Settings files supply configuration beyond the options object. Two options control how they load:

- **`settingSources` / `setting_sources`**: controls which filesystem sources load: user, project, and local. Settings files and CLAUDE.md files arrive through these sources.
- **`settings`**: loads a settings file path or an inline JSON string in either language, and TypeScript also accepts a settings object. Whichever form you pass overrides user, project, and local filesystem settings; only managed policy settings rank higher. The references document the full precedence order under [Settings precedence](agent-sdk-typescript.md#settings-precedence) for TypeScript and [Settings precedence](agent-sdk-python.md#settings-precedence) for Python.

Pass `[]` to disable user, project, and local settings. For more information, see [Use Claude Code features in the SDK](agent-sdk-claude-code-features.md).


[​](#choose-a-model)

Choose a model

Unless the `model` option, your settings, or your environment selects a model, a new session starts on [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md#default-model-setting). For the order of those sources, see [Setting your model](../02-Claude-Code-CLI/model-config.md#setting-your-model). Set `model` to pin a specific model, or to pick a smaller one for faster, cheaper agents. The value takes a model alias or a full model name; aliases and the versions they resolve to are listed under [Model aliases](../02-Claude-Code-CLI/model-config.md#model-aliases). Set `fallbackModel` (TypeScript) or `fallback_model` (Python) to name a backup model. When the primary is overloaded or unavailable, the session switches to the backup. The primary is retried at the start of each user turn, so the session returns to it once the outage passes. In either language, the option accepts a single model or a comma-separated list of backups. For the order and the chain cap, see [Fallback model chains](../02-Claude-Code-CLI/model-config.md#fallback-model-chains). In TypeScript, a fallback equal to `model` throws an error at startup. The examples below show a fallback list in TypeScript and a single fallback in Python:

TypeScript

Python

```python
const options = {
  model: "claude-fable-5",
  fallbackModel: "claude-opus-5,claude-sonnet-5",
};
```

```python
options = ClaudeAgentOptions(
    model="claude-fable-5",
    fallback_model="claude-opus-5",
)
```

The [Messages API](https://code.claude.com/docs/en/04-API-Reference/Other/api-messages-2dd213ca95.md) request parameters `temperature`, `top_p`, and `max_tokens` have no fields on the options object in either language. Set the [effort level](agent-sdk-agent-loop.md#effort-level) or a [spend cap](#limit-turns-and-spend) instead, or call the Messages API when you need those parameters directly.


[​](#set-environment-variables)

Set environment variables

The `env` option sets environment variables for the Claude Code process that runs your session. Whether your values replace the inherited environment or merge over it differs by language:

- **TypeScript**: `env` replaces the subprocess environment
- **Python**: the SDK merges your values over the inherited environment, and your values override the inherited ones

In TypeScript, spread `process.env` into `env` to keep inherited variables such as `PATH`, `HOME`, and `ANTHROPIC_API_KEY`. When you leave `env` unset, the subprocess inherits your environment in both languages. The example routes API traffic through a gateway by setting `ANTHROPIC_BASE_URL`.

TypeScript

Python

```python
const options = {
  env: { ...process.env, ANTHROPIC_BASE_URL: "https://gateway.example.com" },
};
```

```python
options = ClaudeAgentOptions(
    env={"ANTHROPIC_BASE_URL": "https://gateway.example.com"},
)
```

The variables you pass can also configure Claude Code itself. For the variables the Claude Code process reads, see [Environment variables](../02-Claude-Code-CLI/env-vars.md). To tune API timeouts and stall detection this way, follow the Handle slow or stalled API responses section in the [TypeScript reference](agent-sdk-typescript.md#handle-slow-or-stalled-api-responses) or the [Python reference](agent-sdk-python.md#handle-slow-or-stalled-api-responses).


[​](#set-the-working-directory)

Set the working directory

Set `cwd` to run the session in a specific directory. When you leave `cwd` unset, the session runs in your process’s working directory. Neither SDK has a setter for `cwd`. To run in a different directory, start another session with that `cwd`. Claude Code reads the working directory to determine:

- **Project settings and hooks**: which project’s [settings and hooks load](agent-sdk-claude-code-features.md)
- **Skills**: where [session skills are discovered](agent-sdk-skills.md)
- **Session storage**: which project a [stored session belongs to](agent-sdk-session-storage.md)

To let tools reach files outside the working directory, add paths with `additionalDirectories` (TypeScript) or `add_dirs` (Python). For the scope of that grant, see [Additional directories grant file access, not configuration](../02-Claude-Code-CLI/permissions.md#additional-directories-grant-file-access-not-configuration).


[​](#limit-turns-and-spend)

Limit turns and spend

Cap turns and spend with `maxTurns` / `max_turns` and `maxBudgetUsd` / `max_budget_usd`. Both caps are off when unset. When a session hits a cap, the run ends with a result message whose subtype names the cap, `error_max_turns` or `error_max_budget_usd`. What happens next differs by input mode:

- **Single-shot `query()`**: the SDK yields the cap result and then raises, so wrap the loop in a try block to continue past the error
- **Streaming input**: the session stays alive past a cap result, and the max-turns count starts over for each queued message. The budget total accumulates across messages, and once spend reaches the cap, later messages in the same conversation end with the same budget result. A [`/clear`](agent-sdk-cost-tracking.md) starts the budget over

The two caps treat `0` differently:

- **`maxTurns` / `max_turns`**: `0` runs the session without a turn limit, the same as leaving the option unset
- **`maxBudgetUsd` / `max_budget_usd`**: the CLI rejects `0` as an invalid amount at startup, and the session never runs

For more information about both caps, including subagent spend, see [Turns and budget](agent-sdk-agent-loop.md#turns-and-budget).


[​](#change-configuration-mid-session)

Change configuration mid-session

When you start a session with [streaming input](agent-sdk-streaming-vs-single-mode.md), you can switch its model and permission mode while it runs. Where you call the setters differs by language:

- **TypeScript**: methods on the object `query()` returns
- **Python**: methods on [`ClaudeSDKClient`](agent-sdk-python.md#claudesdkclient), since `query()` returns a plain iterator without control methods

Both languages have the same setters:

- **`setModel()` / `set_model()`**: switches the model. Call it with no model to switch to [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md#default-model-setting) rather than the `model` you passed in options.
- **`setPermissionMode()` / `set_permission_mode()`**: switches the permission mode

TypeScript also has `applyFlagSettings()` and `updateSettings()`:

- **`applyFlagSettings()`**: applies settings at runtime, as in `await session.applyFlagSettings({ effortLevel: "high" })`. The method takes settings file keys rather than options fields, so check the [`applyFlagSettings()` reference](agent-sdk-typescript.md#applyflagsettings) for the schema and for which keys take effect mid-session.
- **`updateSettings()`**: writes one allowlisted key to a settings file. The [`updateSettings()` reference](agent-sdk-typescript.md#updatesettings) names the key each source accepts and the version floors.
  - Pass `"localSettings"` to write the project’s local settings file, as in `await session.updateSettings("localSettings", { outputStyle: "Explanatory" })`. The written key takes effect on the session’s next request and persists for later sessions that load `local` settings.
  - Pass `"userSettings"` to write `effortLevel`, the only key that source accepts. Claude Code saves it as the default effort level for the session’s current model, and the running session’s effort doesn’t change.

The example below runs a two-turn session, changes the configuration between the turns, and prints the model that answered each turn. In TypeScript, the prompt stream holds the second message until the setters have run, and the second turn runs on the new model.

TypeScript

Python

```python
import { query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";

function userMessage(text: string): SDKUserMessage {
  return { type: "user", message: { role: "user", content: text }, parent_tool_use_id: null };
}

// Hold the second prompt until the setters have run.
let startSecondTurn!: () => void;
const secondTurnReady = new Promise<void>((resolve) => {
  startSecondTurn = resolve;
});

async function* turnPrompts(): AsyncGenerator<SDKUserMessage, void> {
  yield userMessage("Reply with exactly: ready");
  await secondTurnReady;
  yield userMessage("Reply with exactly: done");
}

const session = query({
  prompt: turnPrompts(),
  options: {
    model: "claude-sonnet-5",
  },
});

let turnModel = "";
let completedTurns = 0;

for await (const message of session) {
  if (message.type === "assistant") {
    turnModel = message.message.model;
  } else if (message.type === "result") {
    completedTurns += 1;
    if (completedTurns === 1) {
      console.log(`First turn model: ${turnModel}`);
      await session.setModel("claude-opus-5");
      await session.setPermissionMode("acceptEdits");
      startSecondTurn();
    } else {
      console.log(`Second turn model: ${turnModel}`);
      break;
    }
  }
}
```

```python
import asyncio

from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, ClaudeSDKClient

async def main():
    options = ClaudeAgentOptions(model="claude-sonnet-5")

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Reply with exactly: ready")
        first_model = ""
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                first_model = message.model

        await client.set_model("claude-opus-5")
        await client.set_permission_mode("acceptEdits")

        await client.query("Reply with exactly: done")
        second_model = ""
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                second_model = message.model

    print(f"First turn model: {first_model}")
    print(f"Second turn model: {second_model}")

asyncio.run(main())
```

On the Claude API, the program prints `First turn model: claude-sonnet-5`, then `Second turn model: claude-opus-5` after the switch.

Each model has its own prompt cache, so after a mid-session switch the next request recomputes the full conversation uncached at the new model’s rates. For more information, see [Switching models](../02-Claude-Code-CLI/prompt-caching.md#switching-models).


[​](#configure-specific-features)

Configure specific features

The table below maps each option to the feature it configures. For options this page doesn’t cover, see the [TypeScript](agent-sdk-typescript.md#options) and [Python](agent-sdk-python.md#claudeagentoptions) references. If you know your goal but not which option serves it, start from [Choose the right feature](agent-sdk-claude-code-features.md#choose-the-right-feature).

| TypeScript                | Python                      | Controls                                 | Covered in                                                                                                                                                                                                                       |
|---------------------------|-----------------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `permissionMode`          | `permission_mode`           | What the agent can do without approval   | [Configure permissions](agent-sdk-permissions.md)                                                                                                                                                                          |
| `allowedTools`            | `allowed_tools`             | Which tool calls are pre-approved        | [Configure permissions](agent-sdk-permissions.md)                                                                                                                                                                          |
| `canUseTool`              | `can_use_tool`              | Your approval callback for tool calls    | [Handle tool approval requests](agent-sdk-user-input.md#handle-tool-approval-requests)                                                                                                                                     |
| `systemPrompt`            | `system_prompt`             | The agent’s instructions                 | [Modifying system prompts](agent-sdk-modifying-system-prompts.md)                                                                                                                                                          |
| `settingSources`          | `setting_sources`           | Which filesystem settings load           | [Use Claude Code features in the SDK](agent-sdk-claude-code-features.md)                                                                                                                                                   |
| `mcpServers`              | `mcp_servers`               | External tool servers                    | [Connect to external tools with MCP](agent-sdk-mcp.md)                                                                                                                                                                     |
| `agents`                  | `agents`                    | Subagent definitions                     | [Subagents](agent-sdk-subagents.md)                                                                                                                                                                                        |
| `hooks`                   | `hooks`                     | Callbacks at lifecycle points            | [Hooks](agent-sdk-hooks.md)                                                                                                                                                                                                |
| `skills`                  | `skills`                    | Which skills load                        | [Extend agents with skills](agent-sdk-skills.md)                                                                                                                                                                           |
| `plugins`                 | `plugins`                   | Which plugins load                       | [Plugins](agent-sdk-plugins.md)                                                                                                                                                                                            |
| `outputFormat`            | `output_format`             | Structured output schemas                | [Structured outputs](agent-sdk-structured-outputs.md)                                                                                                                                                                      |
| `resume`                  | `resume`                    | Continuing a stored session              | [Sessions](agent-sdk-sessions.md)                                                                                                                                                                                          |
| `forkSession`             | `fork_session`              | Branching a session                      | [Sessions](agent-sdk-sessions.md)                                                                                                                                                                                          |
| `sessionStore`            | `session_store`             | External session persistence             | [Session storage](agent-sdk-session-storage.md)                                                                                                                                                                            |
| `enableFileCheckpointing` | `enable_file_checkpointing` | Rewindable file edits                    | [File checkpointing](agent-sdk-file-checkpointing.md)                                                                                                                                                                      |
| `effort`                  | `effort`                    | How much work Claude puts into responses | [Effort level](agent-sdk-agent-loop.md#effort-level)                                                                                                                                                                       |
| `sandbox`                 | `sandbox`                   | Sandbox behavior for tool execution      | [TypeScript](agent-sdk-typescript.md#sandbox-configuration) and [Python](agent-sdk-python.md#sandbox-configuration) references, with deployment context in [Secure deployment](agent-sdk-secure-deployment.md) |


[​](#next-steps)

Next steps

To see configuration composed into working agents:

- **[Quickstart](agent-sdk-quickstart.md)**: build and run a first agent end to end
- **[Examples](agent-sdk-examples.md)**: find a complete, runnable project or a guided Claude Cookbook recipe that matches what you want to build
- **[Multi-tenant isolation](agent-sdk-hosting.md#multi-tenant-isolation)**: isolate each tenant’s settings and memory with `settingSources` / `setting_sources`, `env`, and `cwd`

---

## Track cost and usage - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/cost-tracking
Path: 05-Agent-SDK/agent-sdk-cost-tracking.md

## On this page

- [Understand token usage](#understand-token-usage)
- [Track costs in streaming input mode](#track-costs-in-streaming-input-mode)
- [Get the total cost of a query](#get-the-total-cost-of-a-query)
- [Track per-step and per-model usage](#track-per-step-and-per-model-usage)
  - [Track per-step usage](#track-per-step-usage)
  - [Break down usage per model](#break-down-usage-per-model)
- [Accumulate costs across multiple calls](#accumulate-costs-across-multiple-calls)
- [Handle errors, caching, and output token counts](#handle-errors-caching-and-output-token-counts)
  - [Read output tokens from the result message](#read-output-tokens-from-the-result-message)
  - [Track costs on failed conversations](#track-costs-on-failed-conversations)
  - [Recover totals after a session crash](#recover-totals-after-a-session-crash)
  - [Track cache tokens](#track-cache-tokens)
  - [Extend the prompt cache TTL to one hour](#extend-the-prompt-cache-ttl-to-one-hour)
- [Related documentation](#related-documentation)

Control and observability

# Track cost and usage

Copy pageCopy page

Learn how to track token usage, estimate costs, and configure prompt caching with the Claude Agent SDK.

Copy pageCopy page

The Claude Agent SDK provides detailed token usage information for each interaction with Claude. This guide explains how to properly track usage and understand cost reporting, especially when dealing with parallel tool uses and multi-step conversations. For complete API documentation, see the [TypeScript SDK reference](agent-sdk-typescript.md) and [Python SDK reference](agent-sdk-python.md).

The `total_cost_usd` and `costUSD` fields are client-side estimates, not authoritative billing data. The SDK computes them locally from a price table bundled at build time, unless a [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) table is in effect. They can drift from what you are actually billed when:

- pricing changes
- the installed SDK version does not recognize a model
- billing rules apply that the client cannot model

One billing rule the SDK does model is [data residency pricing](../17-Billing-Plans/about-claude-pricing.md#data-residency-pricing). When a response’s `usage` reports `inference_geo: "us"`, the SDK multiplies the list price of that response’s tokens by 1.1. Per-request fees such as web search aren’t multiplied. Requires TypeScript Agent SDK v0.3.239 or later, or Python Agent SDK v0.2.144 or later.Use these fields for development insight and approximate budgeting. For authoritative billing, use the [Usage and Cost API](../04-API-Reference/Guides/build-with-claude-usage-cost-api.md) or the Usage page in the [Claude Console](../04-API-Reference/Other/playground.md). Do not bill end users or trigger financial decisions from these fields.


[​](#understand-token-usage)

Understand token usage

The TypeScript and Python SDKs expose the same usage data with different field names:

- **TypeScript** provides per-step token breakdowns on each assistant message (`message.message.id`, `message.message.usage`), per-model cost via `modelUsage` on the result message, and a cumulative total on the result message.
- **Python** provides per-step token breakdowns on each assistant message as `message.usage` and `message.message_id`, per-model cost via `model_usage` on the result message, and the cumulative total on the result message as `total_cost_usd`.

Both SDKs use the same underlying cost model and expose the same granularity. The difference is in field naming and where per-step usage is nested. Cost tracking depends on understanding how the SDK scopes usage data:

- **`query()` call:** one invocation of the SDK’s `query()` function. A single call can involve multiple steps: Claude responds, uses tools, gets results, and responds again. Each call produces one [`result`](agent-sdk-typescript.md#sdkresultmessage) message at the end, except in [streaming input mode](agent-sdk-streaming-vs-single-mode.md), where one `query()` call carries multiple user turns and each turn emits its own `result` message.
- **Step:** a single request/response cycle within a `query()` call. Each step produces assistant messages with token usage.
- **Session:** a series of `query()` calls linked by a session ID through the `resume` option. A resumed call’s results report the session’s whole spend, not just that call’s own. See [Accumulate costs across multiple calls](#accumulate-costs-across-multiple-calls) for how the totals carry over.

The following diagram shows the message stream from a single `query()` call, with token usage reported at each step and the cumulative estimate at the end:

1

Each step produces assistant messages

When Claude responds, it sends one or more assistant messages. In TypeScript, each assistant message contains a nested `BetaMessage` (accessed via `message.message`) with an `id` and a [`usage`](../04-API-Reference/Endpoints/messages.md) object with token counts (`input_tokens`, `output_tokens`). In Python, the `AssistantMessage` dataclass exposes the same data directly via `message.usage` and `message.message_id`. When Claude uses multiple tools in one turn, all messages in that turn share the same ID, so deduplicate by ID to avoid double-counting.

2

The result message provides the cumulative estimate

When the `query()` call completes, the SDK emits a result message with `total_cost_usd` and cumulative `usage`, typed as [`SDKResultMessage`](agent-sdk-typescript.md#sdkresultmessage) in TypeScript and [`ResultMessage`](agent-sdk-python.md#resultmessage) in Python. If you only need the estimated total, you can ignore the per-step usage and read this single value.If you make multiple independent `query()` calls, each result reflects only the cost of that individual call. A call that resumes a session also counts the session’s earlier spend.In streaming input mode, each turn emits its own result message. See [Track costs in streaming input mode](#track-costs-in-streaming-input-mode) for how to read call totals in that mode.


[​](#track-costs-in-streaming-input-mode)

Track costs in streaming input mode

In [streaming input mode](agent-sdk-streaming-vs-single-mode.md), one `query()` call carries multiple user turns and each turn emits its own result message. The result fields differ in scope:

- **`usage`**: covers only that turn, and within it only the main agent loop, not any subagents it ran.
- **`total_cost_usd` and `modelUsage`, or `model_usage` in Python**: carry the running total for the whole call so far, plus any spend restored when the call resumed a session.

In a call where your app never sends `/clear`, `/reset`, or `/new`, read the latest result for call totals rather than summing across results. The running totals start over each time your app sends one of those three commands, and inside a `query()` call nothing else resets them. Three results matter for your accounting:

- **The `/clear` turn’s own result**: covers only what has run since the reset, and carries a new `session_id`.
- **Every later result**: keeps counting from that reset.
- **The last result before each `/clear`**: holds the total for the turns since the previous reset.

To total the whole call, add the last result from before each `/clear` to the call’s final result. Every other result, including the `/clear` turn’s own, is superseded by a later one. In TypeScript, the SDK also emits an [`SDKConversationResetMessage`](agent-sdk-typescript.md#sdkconversationresetmessage) at each reset, so you can detect resets from the stream. In Python, the SDK likewise emits a `ConversationResetMessage`. Before Python SDK v0.2.137, the Python iterator dropped that message, so on those versions count the resets yourself from the `/clear` turns your app sends. `maxBudgetUsd` (TypeScript) or `max_budget_usd` (Python) counts only the call’s own spend: totals restored from a resumed session don’t count against it, and a `/clear` starts the budget over.


[​](#get-the-total-cost-of-a-query)

Get the total cost of a query

The result message, typed as [`SDKResultMessage`](agent-sdk-typescript.md#sdkresultmessage) in TypeScript and [`ResultMessage`](agent-sdk-python.md#resultmessage) in Python, marks the end of the agent loop for a `query()` call. It includes `total_cost_usd`, the cumulative estimated cost across all steps in that call. A call that resumes a session also counts the session’s earlier spend. Two caveats apply when you read the value:

- In Python the field is typed as optional, so check that it isn’t `None` before you read it.
- Success and error results both carry it, though the final result of a [session crash](#recover-totals-after-a-session-crash) may carry it zeroed.

In streaming input mode, read call totals as described in [Track costs in streaming input mode](#track-costs-in-streaming-input-mode). The three result-level fields differ in what they count when the agent spawns [subagents](agent-sdk-subagents.md). Use `modelUsage`, or `model_usage` in Python, for whole-tree token accounting; the `usage` field undercounts as soon as nesting occurs.

| Field                        | Subagent activity                                                                                 |
|------------------------------|---------------------------------------------------------------------------------------------------|
| `usage`                      | Excluded. Counts only the top-level agent loop, so tokens consumed inside subagents are not added |
| `total_cost_usd`             | Included. Counts subagent requests alongside the top-level loop                                   |
| `modelUsage` / `model_usage` | Included. Counts subagent requests alongside the top-level loop, broken down by model             |

In [single message input mode](agent-sdk-streaming-vs-single-mode.md#single-message-input), when background subagents are still running at the end of the final turn, Claude Code waits for them, up to the cap described in [background tasks at exit](../02-Claude-Code-CLI/headless.md#background-tasks-at-exit), before emitting the result. The result’s `total_cost_usd`, `duration_api_ms`, and `modelUsage`, or `model_usage` in Python, include the work done during that wait. The following examples iterate over the message stream from a `query()` call and print the total cost when the `result` message arrives:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type === "result") {
      console.log(`Total cost: $${message.total_cost_usd}`);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, it still carried total_cost_usd and the
  // branch above has already run; connection or process failures yield
  // no result message.
  console.error(`Session ended with an error: ${error}`);
}
```

```python
from claude_agent_sdk import query, ResultMessage
import asyncio


async def main():
    try:
        async for message in query(prompt="Summarize this project"):
            if isinstance(message, ResultMessage):
                print(f"Total cost: ${message.total_cost_usd or 0}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the branch above has already run;
        # connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

To bound how much subagents can add to `total_cost_usd`, set the [depth, concurrency, and spend limits](agent-sdk-subagents.md#cap-subagent-depth-concurrency-and-spend) on the query.


[​](#track-per-step-and-per-model-usage)

Track per-step and per-model usage

The examples in this section use TypeScript field names. In Python, the equivalent fields are [`AssistantMessage.usage`](agent-sdk-python.md#assistantmessage) and `AssistantMessage.message_id` for per-step usage, and [`ResultMessage.model_usage`](agent-sdk-python.md#resultmessage) for per-model breakdowns.


[​](#track-per-step-usage)

Track per-step usage

Each assistant message contains a nested `BetaMessage` (accessed via `message.message`) with an `id` and `usage` object with token counts. When Claude uses tools in parallel, multiple messages share the same `id` with identical usage data. Track which IDs you’ve already counted and skip duplicates to avoid inflated totals.

The deduplicated per-step values are accurate for input and cache tokens. Per-step `output_tokens` is a placeholder, so [read output tokens from the result message](#read-output-tokens-from-the-result-message).

The following example accumulates input tokens across all steps, counting each unique main-loop message ID only once and skipping subagent messages, and reads the output total from the result message, which covers the main loop:

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const seenIds = new Set<string>();
let totalInputTokens = 0;
let resultOutputTokens = 0;

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type === "assistant" && !message.parent_tool_use_id) {
      const msgId = message.message.id;

      // Parallel tool calls share the same ID, only count once
      if (!seenIds.has(msgId)) {
        seenIds.add(msgId);
        totalInputTokens += message.message.usage.input_tokens;
      }
    }
    if (message.type === "result") {
      // Per-step output_tokens is a placeholder; the result message
      // carries the accumulated output total.
      resultOutputTokens = message.usage.output_tokens;
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, so the
  // input total below still reflects the steps that ran before the failure.
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Steps: ${seenIds.size}`);
console.log(`Input tokens: ${totalInputTokens}`);
console.log(`Output tokens: ${resultOutputTokens}`);
```


[​](#break-down-usage-per-model)

Break down usage per model

The result message includes [`modelUsage`](agent-sdk-typescript.md#modelusage), a map of model name to per-model token counts and cost. This is useful when you run multiple models (for example, Haiku for subagents and Opus for the main agent) and want to see where tokens are going. Each entry’s `costBasis` says which price table priced that model’s latest request: `list` for list price, `managed` for a [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) table, or `unknown` when neither matched the model ID. The field requires Claude Code v2.1.246 or later. The following example runs a query and prints the cost and token breakdown for each model used:

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type !== "result") continue;

    for (const [modelName, usage] of Object.entries(message.modelUsage)) {
      console.log(`${modelName}: $${usage.costUSD.toFixed(4)}`);
      console.log(`  Input tokens: ${usage.inputTokens}`);
      console.log(`  Output tokens: ${usage.outputTokens}`);
      console.log(`  Cache read: ${usage.cacheReadInputTokens}`);
      console.log(`  Cache creation: ${usage.cacheCreationInputTokens}`);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the per-model breakdown above has already
  // printed; connection or process failures yield no result message.
  console.error(`Session ended with an error: ${error}`);
}
```


[​](#accumulate-costs-across-multiple-calls)

Accumulate costs across multiple calls

Each `query()` call returns `total_cost_usd` on its results. How you combine the values depends on whether the calls share a session:

- **Independent calls, with no `resume` or `continue` option**: each result covers only its own call, so add the totals yourself, as the examples below do.
- **Calls that resume the same session**: Claude Code saves the session’s totals to its [transcript](../02-Claude-Code-CLI/sessions.md#where-transcripts-are-stored) when the process exits normally and restores them when a later call resumes or forks the session. Each result already includes the session’s earlier spend. Read the latest result for the session total; summing results double-counts the restored spend. Before v2.1.277, a session that you resumed through the SDK or `claude -p` started its totals at zero, so each call’s results covered only that call.

Either way, the combined figure is still a [client-side estimate](#estimates-not-billing). In streaming input mode, read each call’s total as described in [Track costs in streaming input mode](#track-costs-in-streaming-input-mode). For a call that ended in a crash, see [Recover totals after a session crash](#recover-totals-after-a-session-crash). The following examples run two `query()` calls sequentially, add each call’s `total_cost_usd` to a running total, and print both the per-call and combined cost:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Track cumulative cost across multiple query() calls
let totalSpend = 0;

const prompts = [
  "Read the files in src/ and summarize the architecture",
  "List all exported functions in src/auth.ts"
];

for (const prompt of prompts) {
  try {
    for await (const message of query({ prompt })) {
      if (message.type === "result") {
        totalSpend += message.total_cost_usd;
        console.log(`This call: $${message.total_cost_usd}`);
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, this call's cost was already counted;
    // connection or process failures yield no result message. Continue
    // with the next prompt.
    console.error(`Call failed: ${error}`);
  }
}

console.log(`Total spend: $${totalSpend.toFixed(4)}`);
```

```python
from claude_agent_sdk import query, ResultMessage
import asyncio


async def main():
    # Track cumulative cost across multiple query() calls
    total_spend = 0.0

    prompts = [
        "Read the files in src/ and summarize the architecture",
        "List all exported functions in src/auth.ts",
    ]

    for prompt in prompts:
        try:
            async for message in query(prompt=prompt):
                if isinstance(message, ResultMessage):
                    cost = message.total_cost_usd or 0
                    total_spend += cost
                    print(f"This call: ${cost}")
        except Exception as error:
            # A single-shot query() raises after yielding an error result. If
            # the failure was an error result, this call's cost was already
            # counted; connection or process failures yield no result message.
            # Continue with the next prompt.
            print(f"Call failed: {error}")

    print(f"Total spend: ${total_spend:.4f}")


asyncio.run(main())
```


[​](#handle-errors-caching-and-output-token-counts)

Handle errors, caching, and output token counts

For accurate cost tracking, account for the placeholder output count on assistant messages, the tokens a failed conversation consumed, and cache token pricing.


[​](#read-output-tokens-from-the-result-message)

Read output tokens from the result message

Claude Code builds each assistant message from the usage the API reported when the response began, so the message’s `output_tokens` is only the count the API had reported at `message_start`, before the response was generated. One API response can produce several assistant messages, and every one of them carries that same placeholder. The API reports the real output count at the end of the response, and Claude Code adds it to the result message. Read output tokens from the result’s `usage`, or from `modelUsage` for a per-model breakdown. To watch a response’s output count grow while it streams, set `includePartialMessages`, or `include_partial_messages` in Python, and read `usage` from each `message_delta` stream event, typed as [`SDKPartialAssistantMessage`](agent-sdk-typescript.md#sdkpartialassistantmessage) in TypeScript and [`StreamEvent`](agent-sdk-python.md#streamevent) in Python.


[​](#track-costs-on-failed-conversations)

Track costs on failed conversations

Both success and error result messages include `usage` and `total_cost_usd`; in Python both fields are typed as optional, so check that they aren’t `None` before you read them. If a conversation fails midway, you still consumed tokens up to the point of failure. Read cost data from every result message, whether its `subtype` is `success` or one of the error subtypes. On some error results, `usage` reports less than the call spent:

- **`error_during_execution` after a [session crash](#recover-totals-after-a-session-crash)**: every cost field may be zeroed.
- **`error_max_budget_usd`**: `usage` leaves out the response that crossed the budget, while `total_cost_usd` and `modelUsage` include it.

Where you have the choice, account from `total_cost_usd` or `modelUsage` rather than `usage`.


[​](#recover-totals-after-a-session-crash)

Recover totals after a session crash

When the Claude Code process crashes, it emits a final `error_during_execution` result and exits, in single-shot and streaming input mode alike. That result may carry zeroed `usage`, `total_cost_usd`, and `modelUsage`, so recover the call’s totals from what arrived before it. Step 1 recovers the full totals whenever an earlier result exists; the fallback in step 2 recovers only the main loop’s input and cache tokens.

1.  Use the result of the turn before the crash. In streaming input mode, it holds the running total described in [Track costs in streaming input mode](#track-costs-in-streaming-input-mode). Go to step 2 instead when that result can’t help you:
    - The call was single-shot, so no earlier result exists.
    - The crash happened on the first turn.
    - The turn before the crash was the `/clear` itself, so its result covers only the reset.
2.  Sum the `usage` on the assistant messages instead, counting each API response once, as the [Track per-step usage](#track-per-step-usage) example does. In single-shot mode, sum all of them; in streaming input mode, sum the ones that arrived after the last result. This gives you the main loop’s input and cache tokens. Subagent usage isn’t recoverable this way, and neither are output tokens or USD cost, because [per-step `output_tokens` is a placeholder](#read-output-tokens-from-the-result-message).


[​](#track-cache-tokens)

Track cache tokens

The Agent SDK automatically uses [prompt caching](../04-API-Reference/Guides/build-with-claude-prompt-caching.md) to reduce costs on repeated content. You do not need to configure caching yourself. The usage object includes two additional fields for cache tracking:

- `cache_creation_input_tokens`: tokens used to create new cache entries (charged at a higher rate than standard input tokens).
- `cache_read_input_tokens`: tokens read from existing cache entries (charged at a reduced rate).

Track these separately from `input_tokens` to understand caching savings. In TypeScript, these fields are typed on the [`Usage`](agent-sdk-typescript.md#usage) object. In Python, they appear as keys in the [`ResultMessage.usage`](agent-sdk-python.md#resultmessage) dict (for example, `message.usage.get("cache_read_input_tokens", 0)`).


[​](#extend-the-prompt-cache-ttl-to-one-hour)

Extend the prompt cache TTL to one hour

Your own turns fall in the [main conversation TTL bucket](../02-Claude-Code-CLI/prompt-caching.md#which-ttl-each-request-gets), together with the helpers Claude Code runs inline with them. The requests Claude Code makes outside that conversation, such as [subagents](agent-sdk-subagents.md), have a [separate TTL control](../02-Claude-Code-CLI/prompt-caching.md#choose-the-ttl-yourself). Cache entries for your own turns use a 5-minute TTL by default when you authenticate with an API key or run on Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md). If your workload runs many short sessions against the same system prompt and context with gaps longer than 5 minutes between them, the cache expires between sessions and each new session pays full input price. To request a 1-hour TTL on cache writes, set the [`ENABLE_PROMPT_CACHING_1H`](../02-Claude-Code-CLI/env-vars.md) environment variable. You can export it in your shell or container environment, or pass it through `options.env`. The following example enables 1-hour TTL for an agent running on Amazon Bedrock. Because it sets `CLAUDE_CODE_USE_BEDROCK`, it requires working AWS credentials for [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md); without them the query fails.

Python

TypeScript

```python
from claude_agent_sdk import ClaudeAgentOptions, query
import asyncio


async def main():
    options = ClaudeAgentOptions(
        env={
            "CLAUDE_CODE_USE_BEDROCK": "1",
            "ENABLE_PROMPT_CACHING_1H": "1",
        },
    )

    async for message in query(prompt="Summarize this project", options=options):
        print(message)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const options = {
  env: {
    ...process.env,
    CLAUDE_CODE_USE_BEDROCK: "1",
    ENABLE_PROMPT_CACHING_1H: "1",
  },
};

for await (const message of query({ prompt: "Summarize this project", options })) {
  console.log(message);
}
```

Cache writes with a 1-hour TTL are billed at a higher rate than 5-minute writes, so enabling this trades higher write cost for more cache reads. See [prompt caching pricing](../04-API-Reference/Guides/build-with-claude-prompt-caching.md) for details. On a Claude subscription within your plan’s included usage, you get the 1-hour TTL on your own turns, and on some of the helper requests Claude Code makes beside them, without setting this variable, and Claude Code drops those turns to the 5-minute TTL once you’re drawing on [usage credits](https://support.claude.com/en/articles/12429409-extra-usage-for-paid-claude-plans). `ENABLE_PROMPT_CACHING_1H` asks for the 1-hour TTL on every request in both buckets. To choose a TTL for each bucket separately, use these controls instead. Each takes `5m` or `1h` and takes precedence over `ENABLE_PROMPT_CACHING_1H`:

- Main conversation: the `CLAUDE_CODE_PROMPT_CACHE_TTL` [environment variable](../02-Claude-Code-CLI/env-vars.md), or the [`promptCacheTtl`](../02-Claude-Code-CLI/settings-reference.md#promptcachettl) setting
- Everything else: the `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL` environment variable, or the [`subagentPromptCacheTtl`](../02-Claude-Code-CLI/settings-reference.md#subagentpromptcachettl) setting

Setting `promptCacheTtl` to `1h` keeps the 1-hour cache on the main conversation while you’re drawing on usage credits. For the full precedence order, see [choose the TTL yourself](../02-Claude-Code-CLI/prompt-caching.md#choose-the-ttl-yourself).


[​](#related-documentation)

Related documentation

- [TypeScript SDK Reference](agent-sdk-typescript.md) - Complete API documentation
- [SDK Overview](agent-sdk-overview.md) - Getting started with the SDK
- [SDK Permissions](agent-sdk-permissions.md) - Managing tool permissions

---

## Give Claude custom tools - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/custom-tools
Path: 05-Agent-SDK/agent-sdk-custom-tools.md

## On this page

- [Quick reference](#quick-reference)
- [Create a custom tool](#create-a-custom-tool)
  - [Weather tool example](#weather-tool-example)
  - [Make a parameter optional](#make-a-parameter-optional)
  - [Call a custom tool](#call-a-custom-tool)
  - [Add more tools](#add-more-tools)
  - [Add tool annotations](#add-tool-annotations)
- [Control tool access](#control-tool-access)
  - [Configure allowed tools](#configure-allowed-tools)
- [Handle errors](#handle-errors)
- [Return images and resources](#return-images-and-resources)
  - [Images](#images)
  - [Resources](#resources)
- [Return structured data](#return-structured-data)
- [Example: unit converter](#example-unit-converter)
- [Next steps](#next-steps)

Extend with tools

# Give Claude custom tools

Copy pageCopy page

Define custom tools with the Claude Agent SDK’s in-process MCP server so Claude can call your functions, hit your APIs, and perform domain-specific operations.

Copy pageCopy page

Custom tools extend the Agent SDK by letting you define your own functions that Claude can call during a conversation. Using the SDK’s in-process MCP server, you can give Claude access to databases, external APIs, domain-specific logic, or any other capability your application needs.


[​](#quick-reference)

Quick reference

| What you want to do                          | Do this                                                                                                                                                                                                                 |
|:---------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Define a tool                                | Use [`@tool`](agent-sdk-python.md#tool) (Python) or [`tool()`](agent-sdk-typescript.md#tool) (TypeScript) with a name, description, schema, and handler. See [Create a custom tool](#create-a-custom-tool). |
| Make a parameter optional                    | Declare it optional in the schema and apply the default in the handler. See [Make a parameter optional](#make-a-parameter-optional).                                                                                    |
| Register a tool with Claude                  | Wrap in `create_sdk_mcp_server` / `createSdkMcpServer` and pass to `mcpServers` in `query()`. See [Call a custom tool](#call-a-custom-tool).                                                                            |
| Pre-approve a tool                           | Add to your allowed tools. See [Configure allowed tools](#configure-allowed-tools).                                                                                                                                     |
| Remove a built-in tool from Claude’s context | Pass a `tools` array listing only the built-ins you want. See [Configure allowed tools](#configure-allowed-tools).                                                                                                      |
| Let Claude call tools in parallel            | Set `readOnlyHint: true` on tools with no side effects. See [Add tool annotations](#add-tool-annotations).                                                                                                              |
| Control the error message Claude reads       | Return `isError: true` to compose the message instead of surfacing the raw exception. See [Handle errors](#handle-errors).                                                                                              |
| Return images or files                       | Use `image` or `resource` blocks in the content array. See [Return images and resources](#return-images-and-resources).                                                                                                 |
| Return a machine-readable JSON result        | Set `structuredContent` on the result. See [Return structured data](#return-structured-data).                                                                                                                           |
| Scale to many tools                          | Use [tool search](agent-sdk-tool-search.md) to load tools on demand.                                                                                                                                              |


[​](#create-a-custom-tool)

Create a custom tool

A tool is defined by four parts, passed as arguments to the [`tool()`](agent-sdk-typescript.md#tool) helper in TypeScript or the [`@tool`](agent-sdk-python.md#tool) decorator in Python:

- **Name:** a unique identifier Claude uses to call the tool.
- **Description:** what the tool does. Claude reads this to decide when to call it.
- **Input schema:** the arguments the tool accepts, declared per language:
  - **TypeScript**: a [Zod schema](https://zod.dev/). The handler’s `args` take their types from it. Call `.describe()` on a field to give it a description Claude sees.
  - **Python**: a dict mapping names to types, like `{"latitude": float}`, which the SDK converts to JSON Schema for you. Wrap a type in `Annotated`, like `{"latitude": Annotated[float, "Latitude coordinate"]}`, to give the field a description Claude sees. The decorator also accepts a full [JSON Schema](https://json-schema.org/understanding-json-schema/about) dict directly when you need enums, ranges, optional fields, or nested objects.
- **Handler:** the async function that runs when Claude calls the tool. It receives the validated arguments and must return an object with:
  - `content` (required): an array of result blocks, each with a `type` of `"text"`, `"image"`, `"audio"`, `"resource"`, or `"resource_link"`. See [Return images and resources](#return-images-and-resources) for non-text blocks.
  - `structuredContent` (optional): a JSON object holding the result as machine-readable data, returned alongside `content`. See [Return structured data](#return-structured-data).
  - `isError` (optional): set to `true` to signal a tool failure so Claude can react to it. See [Handle errors](#handle-errors).

After defining a tool, wrap it in a server with [`createSdkMcpServer`](agent-sdk-typescript.md#createsdkmcpserver) (TypeScript) or [`create_sdk_mcp_server`](agent-sdk-python.md#create_sdk_mcp_server) (Python). The server runs in-process inside your application, not as a separate process. Python examples on this page that make HTTP requests use [httpx](https://www.python-httpx.org/). Add it with the package manager your project uses:

- Python (uv)

- Python (pip)

```python
uv add httpx
```

```python
pip install httpx
```


[​](#weather-tool-example)

Weather tool example

This example defines a `get_temperature` tool and wraps it in an MCP server, without passing the server to `query`. To run the tool, see [Call a custom tool](#call-a-custom-tool) below.

Python

TypeScript

```python
from typing import Annotated, Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server


# Define a tool: name, description, input schema, handler
@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {
        "latitude": Annotated[float, "Latitude coordinate"],
        "longitude": Annotated[float, "Longitude coordinate"],
    },
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "current": "temperature_2m",
                "temperature_unit": "fahrenheit",
            },
        )
        data = response.json()

    # Return a content array - Claude sees this as the tool result
    return {
        "content": [
            {
                "type": "text",
                "text": f"Temperature: {data['current']['temperature_2m']}°F",
            }
        ]
    }


# Wrap the tool in an in-process MCP server
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature],
)
```

```python
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Define a tool: name, description, input schema, handler
const getTemperature = tool(
  "get_temperature",
  "Get the current temperature at a location",
  {
    latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
    longitude: z.number().describe("Longitude coordinate")
  },
  async (args) => {
    // args is typed from the schema: { latitude: number; longitude: number }
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&current=temperature_2m&temperature_unit=fahrenheit`
    );
    const data: any = await response.json();

    // Return a content array - Claude sees this as the tool result
    return {
      content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
    };
  }
);

// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature]
});
```

See the [`tool()`](agent-sdk-typescript.md#tool) TypeScript reference or the [`@tool`](agent-sdk-python.md#tool) Python reference for full parameter details, including JSON Schema input formats and return value structure.


[​](#make-a-parameter-optional)

Make a parameter optional

To make a parameter optional, declare it optional in the schema and apply the default in the handler:

- **TypeScript**: add `.optional()` to the Zod field.
- **Python**: the dict schema requires every key. Use the JSON Schema form, leave the parameter out of `required`, and read it with `args.get()`. For a typed schema with optional keys, see [TypedDict class](agent-sdk-python.md#input-schema-options).

The [`get_precipitation_chance` tool below](#add-more-tools) shows both patterns.


[​](#call-a-custom-tool)

Call a custom tool

Pass the MCP server you created to `query` via the `mcpServers` option. The key in `mcpServers` becomes the `{server_name}` segment in each tool’s fully qualified name: `mcp__{server_name}__{tool_name}`. List that name in `allowedTools` so the tool runs without a permission prompt. These snippets reuse the `weatherServer` from the [weather tool example](#weather-tool-example) to ask Claude what the weather is in a specific location.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"weather": weather_server},
        allowed_tools=["mcp__weather__get_temperature"],
    )

    async for message in query(
        prompt="What's the temperature in San Francisco?",
        options=options,
    ):
        # ResultMessage is the final message after all tool calls complete
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What's the temperature in San Francisco?",
  options: {
    mcpServers: { weather: weatherServer },
    allowedTools: ["mcp__weather__get_temperature"]
  }
})) {
  // "result" is the final message after all tool calls complete
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

Combine this snippet with the tool and server definitions from the [weather tool example](#weather-tool-example) in one file, `weather.py` or `weather.ts`, then run it from your terminal:

- TypeScript

- Python (uv)

- Python (pip)

```python
npx tsx weather.ts
```

```python
uv run weather.py
```

Activate the virtual environment where you installed the SDK, then run:

```python
python weather.py
```

Claude calls `get_temperature` and the script prints a one-line answer with the current temperature in San Francisco.


[​](#add-more-tools)

Add more tools

A server holds as many tools as you list in its `tools` array. With more than one tool on a server, you can list each one in `allowedTools` individually or use the wildcard `mcp__weather__*` to cover every tool the server exposes. The example below defines a second tool, `get_precipitation_chance`, and replaces the `weatherServer` definition from the [weather tool example](#weather-tool-example) with one that lists both tools in the array.

Python

TypeScript

```python
# Define a second tool for the same server
@tool(
    "get_precipitation_chance",
    "Get the hourly precipitation probability for a location",
    {
        "type": "object",
        "properties": {
            "latitude": {"type": "number"},
            "longitude": {"type": "number"},
            "hours": {
                "type": "integer",
                "minimum": 1,
                "maximum": 24,
                "description": "How many hours of forecast to return",
            },
        },
        # 'hours' is left out of required, so Claude can omit it
        "required": ["latitude", "longitude"],
    },
)
async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
    # 'hours' isn't in required - read it with .get() to fall back to a default
    hours = args.get("hours", 12)
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "hourly": "precipitation_probability",
                "forecast_days": 1,
            },
        )
        data = response.json()
    chances = data["hourly"]["precipitation_probability"][:hours]

    return {
        "content": [
            {
                "type": "text",
                "text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%",
            }
        ]
    }


# Rebuild the server with both tools in the array
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature, get_precipitation_chance],
)
```

```python
// Define a second tool for the same server
const getPrecipitationChance = tool(
  "get_precipitation_chance",
  "Get the hourly precipitation probability for a location",
  {
    latitude: z.number(),
    longitude: z.number(),
    hours: z
      .number()
      .int()
      .min(1)
      .max(24)
      .optional() // .optional() lets Claude omit the parameter
      .describe("How many hours of forecast to return")
  },
  async (args) => {
    const hours = args.hours ?? 12; // Apply the default in the handler
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&hourly=precipitation_probability&forecast_days=1`
    );
    const data: any = await response.json();
    const chances = data.hourly.precipitation_probability.slice(0, hours);

    return {
      content: [{ type: "text", text: `Next ${hours} hours: ${chances.join("%, ")}%` }]
    };
  }
);

// Rebuild the server with both tools in the array
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature, getPrecipitationChance]
});
```

[Tool search](agent-sdk-tool-search.md) is on by default and defers SDK MCP tools: Claude sees each tool’s name in a compact list and loads its full schema on demand. With tool search disabled, every tool in this array consumes context window space on every turn. In TypeScript, pass `alwaysLoad: true` in the `extras` argument of [`tool()`](agent-sdk-typescript.md#tool) or in the options of [`createSdkMcpServer()`](agent-sdk-typescript.md#createsdkmcpserver) to keep a tool’s full schema in the initial prompt.


[​](#add-tool-annotations)

Add tool annotations

[Tool annotations](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations) are optional metadata describing how a tool behaves. Pass them as the fifth argument to `tool()` helper in TypeScript or via the `annotations` keyword argument for the `@tool` decorator in Python. All hint fields are Booleans.

| Field             | Default | Meaning                                                                                                               |
|:------------------|:--------|:----------------------------------------------------------------------------------------------------------------------|
| `readOnlyHint`    | `false` | Tool does not modify its environment. Controls whether the tool can be called in parallel with other read-only tools. |
| `destructiveHint` | `true`  | Tool may perform destructive updates. Informational only.                                                             |
| `idempotentHint`  | `false` | Repeated calls with the same arguments have no additional effect. Informational only.                                 |
| `openWorldHint`   | `true`  | Tool reaches systems outside your process. Informational only.                                                        |

Annotations are metadata, not enforcement. A tool marked `readOnlyHint: true` can still write to disk if that’s what the handler does. Keep the annotation accurate to the handler. This example adds `readOnlyHint` to the `get_temperature` tool from the [weather tool example](#weather-tool-example).

Python

TypeScript

```python
from claude_agent_sdk import tool, ToolAnnotations


@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
    annotations=ToolAnnotations(
        readOnlyHint=True
    ),  # Lets Claude batch this with other read-only calls
)
async def get_temperature(args):
    return {"content": [{"type": "text", "text": "..."}]}
```

```python
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "get_temperature",
  "Get the current temperature at a location",
  { latitude: z.number(), longitude: z.number() },
  async (args) => ({ content: [{ type: "text", text: `...` }] }),
  { annotations: { readOnlyHint: true } } // Lets Claude batch this with other read-only calls
);
```

See `ToolAnnotations` in the [TypeScript](agent-sdk-typescript.md#toolannotations) or [Python](agent-sdk-python.md#toolannotations) reference.


[​](#control-tool-access)

Control tool access

The [weather tool example](#weather-tool-example) registered a server and listed tools in `allowedTools`. This section covers how to scope access when you have multiple tools or want to restrict built-ins. For how tool names are constructed, see [Call a custom tool](#call-a-custom-tool).


[​](#configure-allowed-tools)

Configure allowed tools

The `tools` option and the allowed/disallowed lists affect two layers: availability, which controls whether a tool appears in Claude’s context, and permission, which controls whether a call is approved once Claude attempts it. `tools` and bare-name `disallowedTools` entries change availability. `allowedTools` and scoped `disallowedTools` rules change permission. If you name one of the [task-tracking tools](agent-sdk-todo-tracking.md#model-availability) in `allowedTools`, Claude Code also opts the session in.

| Option                    | Layer        | Effect                                                                                                                                                                                                                                                                |
|:--------------------------|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `tools: ["Read", "Grep"]` | Availability | Only the listed built-ins are in Claude’s context. Unlisted built-ins are removed. MCP tools are unaffected.                                                                                                                                                          |
| `tools: []`               | Availability | All built-ins are removed. Claude can only use your MCP tools.                                                                                                                                                                                                        |
| allowed tools             | Permission   | Listed tools run without a permission prompt. Other unlisted tools remain available; calls go through the [permission flow](agent-sdk-permissions.md).                                                                                                          |
| disallowed tools          | Both         | A bare tool name such as `"Bash"` removes the tool from Claude’s context, the same as omitting it from `tools`. A scoped rule such as `"Bash(rm *)"` leaves the tool in context and denies only calls that match [as written](../02-Claude-Code-CLI/permissions.md#bash-rule-limits). |

To remove a built-in entirely, omit it from `tools` or list its bare name in `disallowedTools` (Python: `disallowed_tools`); both keep the tool out of context so Claude never attempts it. A scoped `disallowedTools` rule blocks matching calls but leaves the tool visible, so Claude may waste a turn trying it. See [Configure permissions](agent-sdk-permissions.md) for the full evaluation order.


[​](#handle-errors)

Handle errors

A handler error doesn’t stop the agent loop. The SDK’s in-process MCP server catches uncaught exceptions and returns them as error results, so how you report an error determines what Claude reads, not whether the query fails:

| What happens                                                                             | Result                                                                                                                                    |
|:-----------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------|
| Handler throws an uncaught exception                                                     | The MCP server converts it to an error result carrying the raw exception message. Claude sees that message, and the agent loop continues. |
| Handler catches the error and returns `isError: true` (TS) / `"is_error": True` (Python) | Claude sees the message you compose. You can add context the raw exception lacks, such as which request failed or what to try instead.    |

In both cases Claude can retry, try a different tool, or explain the failure. Catch errors yourself when the raw exception message isn’t enough for Claude to act on. The example below catches two kinds of failures inside the handler and composes the error message Claude reads. A non-200 HTTP status is caught from the response and returned as an error result. A network error or invalid JSON is caught by the surrounding `try/except` (Python) or `try/catch` (TypeScript) and also returned as an error result. In both cases Claude receives a message that describes the failure instead of a bare exception string.

Python

TypeScript

```python
import json
import httpx
from typing import Any
from claude_agent_sdk import tool


@tool(
    "fetch_data",
    "Fetch data from an API",
    {"endpoint": str},  # Simple schema
)
async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(args["endpoint"])
            if response.status_code != 200:
                # Return the failure as a tool result so Claude can react to it.
                # is_error marks this as a failed call rather than odd-looking data.
                return {
                    "content": [
                        {
                            "type": "text",
                            "text": f"API error: {response.status_code} {response.reason_phrase}",
                        }
                    ],
                    "is_error": True,
                }

            data = response.json()
            return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]}
    except Exception as e:
        # Composes the message Claude reads. An uncaught exception would
        # reach Claude as the raw str(e) with no context.
        return {
            "content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
            "is_error": True,
        }
```

```python
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_data",
  "Fetch data from an API",
  {
    endpoint: z.string().url().describe("API endpoint URL")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);

      if (!response.ok) {
        // Return the failure as a tool result so Claude can react to it.
        // isError marks this as a failed call rather than odd-looking data.
        return {
          content: [
            {
              type: "text",
              text: `API error: ${response.status} ${response.statusText}`
            }
          ],
          isError: true
        };
      }

      const data = await response.json();
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(data, null, 2)
          }
        ]
      };
    } catch (error) {
      // Composes the message Claude reads. An uncaught throw would
      // reach Claude as the raw error message with no context.
      return {
        content: [
          {
            type: "text",
            text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`
          }
        ],
        isError: true
      };
    }
  }
);
```


[​](#return-images-and-resources)

Return images and resources

The `content` array in a tool result accepts `text`, `image`, `audio`, `resource`, and `resource_link` blocks. You can mix them in the same response. In TypeScript, the SDK saves audio blocks to disk and Claude receives a text block with the saved file path; in Python, the SDK drops audio blocks from the tool result and logs a warning. Claude receives each resource link block as a text block containing the link’s name, URI, and description. In TypeScript, your application also receives the links themselves as [`resourceLinks`](agent-sdk-typescript.md#sdkmcpresourcelink) on the user message’s `tool_use_result`; in Python, the SDK flattens them to text before the CLI sees the result, so the Python [`resourceLinks` key](agent-sdk-python.md#usermessage) is never produced for in-process tools.


[​](#images)

Images

An image block carries the image bytes inline, encoded as base64. There is no URL field. To return an image that lives at a URL, fetch it in the handler, read the response bytes, and base64-encode them before returning. A PNG, JPEG, GIF, or WebP image reaches Claude as visual input; an image of any other type is saved to disk and Claude receives its file path as text instead.

| Field      | Type      | Notes                                                                      |
|:-----------|:----------|:---------------------------------------------------------------------------|
| `type`     | `"image"` |                                                                            |
| `data`     | `string`  | Base64-encoded bytes. Raw base64 only, no `data:image/...;base64,` prefix  |
| `mimeType` | `string`  | Required. For example `image/png`, `image/jpeg`, `image/webp`, `image/gif` |

Python

TypeScript

```python
import base64
import httpx
from claude_agent_sdk import tool


# Define a tool that fetches an image from a URL and returns it to Claude
@tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})
async def fetch_image(args):
    async with httpx.AsyncClient() as client:  # Fetch the image bytes
        response = await client.get(args["url"])

    return {
        "content": [
            {
                "type": "image",
                "data": base64.b64encode(response.content).decode(
                    "ascii"
                ),  # Base64-encode the raw bytes
                "mimeType": response.headers.get(
                    "content-type", "image/png"
                ),  # Read MIME type from the response
            }
        ]
    }
```

```python
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_image",
  "Fetch an image from a URL and return it to Claude",
  {
    url: z.string().url()
  },
  async (args) => {
    const response = await fetch(args.url); // Fetch the image bytes
    const buffer = Buffer.from(await response.arrayBuffer()); // Read into a Buffer for base64 encoding
    const mimeType = response.headers.get("content-type") ?? "image/png";

    return {
      content: [
        {
          type: "image",
          data: buffer.toString("base64"), // Base64-encode the raw bytes
          mimeType
        }
      ]
    };
  }
);
```


[​](#resources)

Resources

A resource block embeds a piece of content identified by a URI. The actual content rides in the block’s `text` or `blob` field. Use this when your tool produces a generated file or a record from an external system.

| Field               | Type         | Notes                                                                                                                                      |
|:--------------------|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------|
| `type`              | `"resource"` |                                                                                                                                            |
| `resource.uri`      | `string`     | Identifier for the content. Any URI scheme                                                                                                 |
| `resource.text`     | `string`     | The content, if it’s text. Provide this or `blob`, not both                                                                                |
| `resource.blob`     | `string`     | The content base64-encoded, if it’s binary. TypeScript only: the Python SDK drops binary resources from the tool result and logs a warning |
| `resource.mimeType` | `string`     | Optional                                                                                                                                   |

This example shows a resource block returned from inside a tool handler. The SDK doesn’t read from the example’s URI, `file:///tmp/report.md`.

TypeScript

Python

```python
return {
  content: [
    {
      type: "resource",
      resource: {
        uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
        mimeType: "text/markdown",
        text: "# Report\n..." // The actual content, inline
      }
    }
  ]
};
```

```python
return {
    "content": [
        {
            "type": "resource",
            "resource": {
                "uri": "file:///tmp/report.md",  # Not a path the SDK reads
                "mimeType": "text/markdown",
                "text": "# Report\n...",  # The actual content, inline
            },
        }
    ]
}
```

These block shapes come from the MCP `CallToolResult` type. See the [MCP specification](../06-MCP-Tools/Spec-Archive/2025-06-18-server-tools.md#tool-result) for the full definition.


[​](#return-structured-data)

Return structured data

`structuredContent` is an optional JSON object on the result, separate from the `content` array. Use it to return raw values that Claude can read as exact fields instead of parsing them out of a text string or image. When `structuredContent` is set, Claude receives the JSON plus any image or resource blocks from `content`. Text blocks in `content` are not forwarded, since they are assumed to duplicate the structured data. The example below renders a chart as an image block and returns the data points behind it in `structuredContent` from the same handler. In the snippet, `chartPngBuffer` is a `Buffer` holding the rendered PNG bytes.

TypeScript

```python
return {
  content: [
    {
      type: "image",
      data: chartPngBuffer.toString("base64"),
      mimeType: "image/png"
    }
  ],
  structuredContent: {
    series: "temperature_2m",
    unit: "fahrenheit",
    points: [62.1, 63.4, 65.0, 64.2]
  }
};
```

The Python `@tool` decorator forwards only `content` and `is_error` from the handler’s return dict. To return `structuredContent` from Python, run a [standalone MCP server](agent-sdk-mcp.md) instead of an in-process SDK server.


[​](#example-unit-converter)

Example: unit converter

This tool converts values between units of length, temperature, and weight. A user can ask “convert 100 kilometers to miles” or “what is 72°F in Celsius,” and Claude picks the right unit type and units from the request. It demonstrates two patterns:

- **Enum schemas:** `unit_type` is constrained to a fixed set of values. In TypeScript, use `z.enum()`. In Python, the dict schema doesn’t support enums, so the full JSON Schema dict is required.
- **Unsupported input handling:** when a conversion pair isn’t found, the handler returns `isError: true` so Claude can tell the user what went wrong rather than treating a failure as a normal result.

Python

TypeScript

```python
from typing import Any
from claude_agent_sdk import tool, create_sdk_mcp_server


# z.enum() in TypeScript becomes an "enum" constraint in JSON Schema.
# The dict schema has no equivalent, so full JSON Schema is required.
@tool(
    "convert_units",
    "Convert a value from one unit to another",
    {
        "type": "object",
        "properties": {
            "unit_type": {
                "type": "string",
                "enum": ["length", "temperature", "weight"],
                "description": "Category of unit",
            },
            "from_unit": {
                "type": "string",
                "description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds",
            },
            "to_unit": {"type": "string", "description": "Unit to convert to"},
            "value": {"type": "number", "description": "Value to convert"},
        },
        "required": ["unit_type", "from_unit", "to_unit", "value"],
    },
)
async def convert_units(args: dict[str, Any]) -> dict[str, Any]:
    conversions = {
        "length": {
            "kilometers_to_miles": lambda v: v * 0.621371,
            "miles_to_kilometers": lambda v: v * 1.60934,
            "meters_to_feet": lambda v: v * 3.28084,
            "feet_to_meters": lambda v: v * 0.3048,
        },
        "temperature": {
            "celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32,
            "fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9,
            "celsius_to_kelvin": lambda v: v + 273.15,
            "kelvin_to_celsius": lambda v: v - 273.15,
        },
        "weight": {
            "kilograms_to_pounds": lambda v: v * 2.20462,
            "pounds_to_kilograms": lambda v: v * 0.453592,
            "grams_to_ounces": lambda v: v * 0.035274,
            "ounces_to_grams": lambda v: v * 28.3495,
        },
    }

    key = f"{args['from_unit']}_to_{args['to_unit']}"
    fn = conversions.get(args["unit_type"], {}).get(key)

    if not fn:
        return {
            "content": [
                {
                    "type": "text",
                    "text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}",
                }
            ],
            "is_error": True,
        }

    result = fn(args["value"])
    return {
        "content": [
            {
                "type": "text",
                "text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}",
            }
        ]
    }


converter_server = create_sdk_mcp_server(
    name="converter",
    version="1.0.0",
    tools=[convert_units],
)
```

```python
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const convert = tool(
  "convert_units",
  "Convert a value from one unit to another",
  {
    unit_type: z.enum(["length", "temperature", "weight"]).describe("Category of unit"),
    from_unit: z
      .string()
      .describe("Unit to convert from, e.g. kilometers, fahrenheit, pounds"),
    to_unit: z.string().describe("Unit to convert to"),
    value: z.number().describe("Value to convert")
  },
  async (args) => {
    type Conversions = Record<string, Record<string, (v: number) => number>>;

    const conversions: Conversions = {
      length: {
        kilometers_to_miles: (v) => v * 0.621371,
        miles_to_kilometers: (v) => v * 1.60934,
        meters_to_feet: (v) => v * 3.28084,
        feet_to_meters: (v) => v * 0.3048
      },
      temperature: {
        celsius_to_fahrenheit: (v) => (v * 9) / 5 + 32,
        fahrenheit_to_celsius: (v) => ((v - 32) * 5) / 9,
        celsius_to_kelvin: (v) => v + 273.15,
        kelvin_to_celsius: (v) => v - 273.15
      },
      weight: {
        kilograms_to_pounds: (v) => v * 2.20462,
        pounds_to_kilograms: (v) => v * 0.453592,
        grams_to_ounces: (v) => v * 0.035274,
        ounces_to_grams: (v) => v * 28.3495
      }
    };

    const key = `${args.from_unit}_to_${args.to_unit}`;
    const fn = conversions[args.unit_type]?.[key];

    if (!fn) {
      return {
        content: [
          {
            type: "text",
            text: `Unsupported conversion: ${args.from_unit} to ${args.to_unit}`
          }
        ],
        isError: true
      };
    }

    const result = fn(args.value);
    return {
      content: [
        {
          type: "text",
          text: `${args.value} ${args.from_unit} = ${result.toFixed(4)} ${args.to_unit}`
        }
      ]
    };
  }
);

const converterServer = createSdkMcpServer({
  name: "converter",
  version: "1.0.0",
  tools: [convert]
});
```

Once the server is defined, pass it to `query` the same way as the weather example. This example sends three different prompts in a loop to show the same tool handling different unit types. For each response, it inspects `AssistantMessage` objects (which contain the tool calls Claude made during that turn) and prints each `ToolUseBlock` before printing the final `ResultMessage` text. This lets you see when Claude is using the tool versus answering from its own knowledge. Because [tool search](agent-sdk-tool-search.md) is on by default, the output may also include a `ToolSearch` call as Claude loads the deferred tool schema.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    ResultMessage,
    AssistantMessage,
    ToolUseBlock,
)


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"converter": converter_server},
        allowed_tools=["mcp__converter__convert_units"],
    )

    prompts = [
        "Convert 100 kilometers to miles.",
        "What is 72°F in Celsius?",
        "How many pounds is 5 kilograms?",
    ]

    for prompt in prompts:
        try:
            async for message in query(prompt=prompt, options=options):
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, ToolUseBlock):
                            print(f"[tool call] {block.name}({block.input})")
                elif isinstance(message, ResultMessage) and message.subtype == "success":
                    print(f"Q: {prompt}\nA: {message.result}\n")
        except Exception as error:
            # A single-shot query() raises after yielding an error result. Only success
            # results are printed above, so handle the failure here and continue with
            # the next prompt.
            print(f"Call failed: {error}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const prompts = [
  "Convert 100 kilometers to miles.",
  "What is 72°F in Celsius?",
  "How many pounds is 5 kilograms?"
];

for (const prompt of prompts) {
  try {
    for await (const message of query({
      prompt,
      options: {
        mcpServers: { converter: converterServer },
        allowedTools: ["mcp__converter__convert_units"]
      }
    })) {
      if (message.type === "assistant") {
        for (const block of message.message.content) {
          if (block.type === "tool_use") {
            console.log(`[tool call] ${block.name}`, block.input);
          }
        }
      } else if (message.type === "result" && message.subtype === "success") {
        console.log(`Q: ${prompt}\nA: ${message.result}\n`);
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. Only success
    // results are logged above, so handle the failure here and continue with
    // the next prompt.
    console.error(`Call failed: ${error}`);
  }
}
```


[​](#next-steps)

Next steps

You can mix the patterns on this page in the same server: a single server can hold a database tool, an API gateway tool, and an image renderer alongside each other. From here:

- If your server grows to dozens of tools, see [tool search](agent-sdk-tool-search.md) to defer loading them until Claude needs them.
- To connect to external MCP servers (filesystem, GitHub, Slack) instead of building your own, see [Connect MCP servers](agent-sdk-mcp.md).
- To control which tools run automatically versus requiring approval, see [Configure permissions](agent-sdk-permissions.md).

---

## Examples - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/examples
Path: 05-Agent-SDK/agent-sdk-examples.md

## On this page

- [Run a minimal agent first](#run-a-minimal-agent-first)
- [Explore a demo application](#explore-a-demo-application)
- [Work through a Python recipe](#work-through-a-python-recipe)

Build agents

# Examples

Copy pageCopy page

Find a complete, runnable Agent SDK project or a guided recipe in the Claude Cookbook that matches what you want to build.

Copy pageCopy page

This page routes you to complete, runnable Agent SDK projects and guided Claude Cookbook recipes. The applications live in the [`claude-agent-sdk-demos`](https://github.com/anthropics/claude-agent-sdk-demos) repo, and Python recipes live in the [Claude Cookbook](https://platform.claude.com/cookbook).


[​](#run-a-minimal-agent-first)

Run a minimal agent first

If you haven’t built anything with the SDK yet, start with one of these before a full application:

- [Agent SDK quickstart](agent-sdk-quickstart.md): build your first working agent in TypeScript or Python, with setup steps included. The agent finds and fixes bugs in a sample file.
- [Hello World](https://github.com/anthropics/claude-agent-sdk-demos/tree/main/hello-world): a minimal TypeScript project to clone when you want to start from repo code


[​](#explore-a-demo-application)

Explore a demo application

The applications in [`claude-agent-sdk-demos`](https://github.com/anthropics/claude-agent-sdk-demos) are demos for local development, from an email client to a multi-agent research system. Clone the demo whose shape matches what you’re building.


[​](#work-through-a-python-recipe)

Work through a Python recipe

The Claude Cookbook’s Agent SDK series is a sequence of recipes, each a Python notebook, that progresses from a simple research agent to sophisticated multi-agent systems. Each notebook builds on the previous one, introducing new concepts and capabilities. Start with [the one-liner research agent](https://platform.claude.com/cookbook/claude-agent-sdk-00-the-one-liner-research-agent) and work forward. For recipes across Claude products, see the full [Claude Cookbook](https://platform.claude.com/cookbook).

---

## Rewind file changes with checkpointing - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/file-checkpointing
Path: 05-Agent-SDK/agent-sdk-file-checkpointing.md

## On this page

- [How checkpointing works](#how-checkpointing-works)
- [Implement checkpointing](#implement-checkpointing)
- [Common patterns](#common-patterns)
  - [Checkpoint before risky operations](#checkpoint-before-risky-operations)
  - [Multiple restore points](#multiple-restore-points)
- [Try it out](#try-it-out)
- [Limitations](#limitations)
- [Troubleshooting](#troubleshooting)
  - [Checkpointing options not recognized](#checkpointing-options-not-recognized)
  - [User messages don’t have UUIDs](#user-messages-don%E2%80%99t-have-uuids)
  - [”No file checkpoint found for this message” error](#%E2%80%9Dno-file-checkpoint-found-for-this-message%E2%80%9D-error)
  - [”File rewinding is not enabled” error](#%E2%80%9Dfile-rewinding-is-not-enabled%E2%80%9D-error)
  - [”ProcessTransport is not ready for writing” error](#%E2%80%9Dprocesstransport-is-not-ready-for-writing%E2%80%9D-error)
- [Next steps](#next-steps)

Control and observability

# Rewind file changes with checkpointing

Copy pageCopy page

Track file changes during agent sessions and restore files to any previous state

Copy pageCopy page

File checkpointing tracks file modifications made through the [Write](../02-Claude-Code-CLI/tools-reference.md#write-tool-behavior), [Edit](../02-Claude-Code-CLI/tools-reference.md#edit-tool-behavior), and [NotebookEdit](../02-Claude-Code-CLI/tools-reference.md#notebookedit-tool-behavior) tools during an agent session, allowing you to rewind files to any previous state. To watch a rewind restore a file, jump to the [interactive example](#try-it-out).

Only changes made through the Write, Edit, and NotebookEdit tools are tracked. Changes made through Bash commands (like `echo > file.txt` or `sed -i`) are not captured by the checkpoint system, and neither are edits a [subagent](agent-sdk-subagents.md) applies, except a [skill with `context: fork`](../08-Plugins-Skills/skills.md#run-skills-in-a-subagent) that runs in the foreground.


[​](#how-checkpointing-works)

How checkpointing works

When you enable file checkpointing, the SDK creates backups of files before modifying them through the Write, Edit, or NotebookEdit tools. User messages in the response stream include a checkpoint UUID that you can use as a restore point.

File rewinding restores files on disk to a previous state. It does not rewind the conversation itself. The conversation history and context remain intact after calling `rewindFiles()` (TypeScript) or `rewind_files()` (Python).

When you rewind to a checkpoint, Claude Code deletes the files it created and restores the files it modified to their content at that point. Claude Code skips a tracked path that is a symlink, hard link, or other non-regular file. It also skips a tracked file whose parent directory no longer resolves to its checkpoint-time location, or whose backup it can’t read safely. [`RewindFilesResult`](agent-sdk-typescript.md#rewindfilesresult) counts every skipped path in its `skippedLinks` field. Skipping requires Claude Code v2.1.216 or later; before v2.1.216, a rewind wrote and deleted through links at tracked paths.


[​](#implement-checkpointing)

Implement checkpointing

To use file checkpointing, enable it in your options, capture checkpoint UUIDs from the response stream, then call `rewindFiles()` (TypeScript) or `rewind_files()` (Python) when you need to restore. The examples in this section use the prompt “Refactor the authentication module”. Run them in a project that contains an authentication module, or change the prompt to name files that exist in your project, so you can watch files change and see the rewind restore them. This example enables checkpointing, captures the checkpoint UUID and session ID from the response stream, then resumes the session later to rewind files. The example’s numbered comments match the steps that follow.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    UserMessage,
    ResultMessage,
)


async def main():
    # Step 1: Enable checkpointing
    options = ClaudeAgentOptions(
        enable_file_checkpointing=True,
        permission_mode="acceptEdits",  # Auto-accept file edits without prompting
        extra_args={
            "replay-user-messages": None
        },  # Required to receive checkpoint UUIDs in the response stream
    )

    checkpoint_id = None
    session_id = None

    # Run the query and capture checkpoint UUID and session ID
    async with ClaudeSDKClient(options) as client:
        await client.query("Refactor the authentication module")

        # Step 2: Capture checkpoint UUID from the first user message
        async for message in client.receive_response():
            if isinstance(message, UserMessage) and message.uuid and not checkpoint_id:
                checkpoint_id = message.uuid
            if isinstance(message, ResultMessage) and not session_id:
                session_id = message.session_id

    # Step 3: Later, rewind by resuming the session with an empty prompt
    if checkpoint_id and session_id:
        async with ClaudeSDKClient(
            ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)
        ) as client:
            await client.query("")  # Empty prompt to open the connection
            async for message in client.receive_response():
                await client.rewind_files(checkpoint_id)
                break
        print(f"Rewound to checkpoint: {checkpoint_id}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  // Step 1: Enable checkpointing
  const opts = {
    enableFileCheckpointing: true,
    permissionMode: "acceptEdits" as const, // Auto-accept file edits without prompting
    extraArgs: { "replay-user-messages": null } // Required to receive checkpoint UUIDs in the response stream
  };

  const response = query({
    prompt: "Refactor the authentication module",
    options: opts
  });

  let checkpointId: string | undefined;
  let sessionId: string | undefined;

  // Step 2: Capture checkpoint UUID from the first user message
  try {
    for await (const message of response) {
      if (message.type === "user" && message.uuid && !checkpointId) {
        checkpointId = message.uuid;
      }
      if ("session_id" in message && !sessionId) {
        sessionId = message.session_id;
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, sessionId and checkpointId were already
    // captured by the loop above; connection or process failures yield no
    // result message.
    console.error(`Session ended with an error: ${error}`);
  }

  // Step 3: Later, rewind by resuming the session with an empty prompt
  if (checkpointId && sessionId) {
    const rewindQuery = query({
      prompt: "", // Empty prompt to open the connection
      options: { ...opts, resume: sessionId }
    });

    for await (const msg of rewindQuery) {
      await rewindQuery.rewindFiles(checkpointId);
      break;
    }
    console.log(`Rewound to checkpoint: ${checkpointId}`);
  }
}

main();
```

1

Enable checkpointing

Configure your SDK options to enable checkpointing and receive checkpoint UUIDs:

| Option                   | Python                                      | TypeScript                                    | Description                                      |
|--------------------------|---------------------------------------------|-----------------------------------------------|--------------------------------------------------|
| Enable checkpointing     | `enable_file_checkpointing=True`            | `enableFileCheckpointing: true`               | Tracks file changes for rewinding                |
| Receive checkpoint UUIDs | `extra_args={"replay-user-messages": None}` | `extraArgs: { 'replay-user-messages': null }` | Required to get user message UUIDs in the stream |

The lead example under [Implement checkpointing](#implement-checkpointing) also sets the permission mode to `acceptEdits`, which approves the agent’s file edits without a prompt. For more information, see [Permission modes](agent-sdk-permissions.md#permission-modes).

2

Capture checkpoint UUID and session ID

With the `replay-user-messages` option set, each user message in the response stream has a UUID that serves as a checkpoint.For most use cases, capture the first user message UUID (`message.uuid`); rewinding to it restores the tracked files to their original state. To store multiple checkpoints and rewind to intermediate states, see [Multiple restore points](#multiple-restore-points).Capturing the session ID (`message.session_id`) is optional; you only need it if you want to rewind later, after the stream completes. If you’re calling `rewindFiles()` immediately while still processing messages (as the example in [Checkpoint before risky operations](#checkpoint-before-risky-operations) does), you can skip capturing the session ID.

3

Rewind files

To rewind after the stream completes, resume the session with an empty prompt and call `rewind_files()` (Python) or `rewindFiles()` (TypeScript) with your checkpoint UUID. Enable checkpointing on the resumed session’s options too, and call rewind from inside the response loop. You can also rewind during the stream. See [Checkpoint before risky operations](#checkpoint-before-risky-operations) for that pattern.If you capture the session ID and checkpoint ID, you can also rewind from the CLI. This command requires the `claude` executable, which comes from [installing Claude Code](../01-Getting-Started/setup.md). The SDK enables checkpointing for you, but when you run `claude -p` directly you must set the `CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING` environment variable:

```python
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true claude -p --resume <session-id> --rewind-files <checkpoint-uuid>
```

The `--rewind-files` flag doesn’t appear in `claude --help` output, but the CLI accepts it as shown. When the rewind succeeds, the command prints `Files rewound to state at message <checkpoint-uuid>` and exits without sending a prompt.


[​](#common-patterns)

Common patterns

These patterns show different ways to capture and use checkpoint UUIDs depending on your use case.


[​](#checkpoint-before-risky-operations)

Checkpoint before risky operations

This pattern keeps only the most recent checkpoint UUID, updating it before each agent turn. If something goes wrong during processing, you can immediately rewind to the last safe state and break out of the loop. Before running this example, replace `your_revert_condition` (Python) or `yourRevertCondition` (TypeScript) with your own check, such as error detection or a validation failure; the placeholder is not defined in the example.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, UserMessage


async def main():
    options = ClaudeAgentOptions(
        enable_file_checkpointing=True,
        permission_mode="acceptEdits",
        extra_args={"replay-user-messages": None},
    )

    safe_checkpoint = None

    async with ClaudeSDKClient(options) as client:
        await client.query("Refactor the authentication module")

        async for message in client.receive_response():
            # Update checkpoint before each agent turn starts
            # This overwrites the previous checkpoint. Only keep the latest
            if isinstance(message, UserMessage) and message.uuid:
                safe_checkpoint = message.uuid

            # Decide when to revert based on your own logic
            # For example: error detection, validation failure, or user input
            if your_revert_condition and safe_checkpoint:
                await client.rewind_files(safe_checkpoint)
                # Exit the loop after rewinding, files are restored
                break


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  const response = query({
    prompt: "Refactor the authentication module",
    options: {
      enableFileCheckpointing: true,
      permissionMode: "acceptEdits" as const,
      extraArgs: { "replay-user-messages": null }
    }
  });

  let safeCheckpoint: string | undefined;

  for await (const message of response) {
    // Update checkpoint before each agent turn starts
    // This overwrites the previous checkpoint. Only keep the latest
    if (message.type === "user" && message.uuid) {
      safeCheckpoint = message.uuid;
    }

    // Decide when to revert based on your own logic
    // For example: error detection, validation failure, or user input
    if (yourRevertCondition && safeCheckpoint) {
      await response.rewindFiles(safeCheckpoint);
      // Exit the loop after rewinding, files are restored
      break;
    }
  }
}

main();
```


[​](#multiple-restore-points)

Multiple restore points

If Claude makes changes across multiple turns, you might want to rewind to a specific point rather than all the way back. For example, if Claude refactors a file in turn one and adds tests in turn two, you might want to keep the refactor but undo the tests. This pattern stores all checkpoint UUIDs in an array with metadata. After the session completes, you can rewind to any previous checkpoint:

Python

TypeScript

```python
import asyncio
from dataclasses import dataclass
from datetime import datetime
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    UserMessage,
    ResultMessage,
)


# Store checkpoint metadata for better tracking
@dataclass
class Checkpoint:
    id: str
    description: str
    timestamp: datetime


async def main():
    options = ClaudeAgentOptions(
        enable_file_checkpointing=True,
        permission_mode="acceptEdits",
        extra_args={"replay-user-messages": None},
    )

    checkpoints = []
    session_id = None

    async with ClaudeSDKClient(options) as client:
        await client.query("Refactor the authentication module")

        async for message in client.receive_response():
            if isinstance(message, UserMessage) and message.uuid:
                checkpoints.append(
                    Checkpoint(
                        id=message.uuid,
                        description=f"After turn {len(checkpoints) + 1}",
                        timestamp=datetime.now(),
                    )
                )
            if isinstance(message, ResultMessage) and not session_id:
                session_id = message.session_id

    # Later: rewind to any checkpoint by resuming the session
    if checkpoints and session_id:
        target = checkpoints[0]  # Pick any checkpoint
        async with ClaudeSDKClient(
            ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)
        ) as client:
            await client.query("")  # Empty prompt to open the connection
            async for message in client.receive_response():
                await client.rewind_files(target.id)
                break
        print(f"Rewound to: {target.description}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Store checkpoint metadata for better tracking
interface Checkpoint {
  id: string;
  description: string;
  timestamp: Date;
}

async function main() {
  const opts = {
    enableFileCheckpointing: true,
    permissionMode: "acceptEdits" as const,
    extraArgs: { "replay-user-messages": null }
  };

  const response = query({
    prompt: "Refactor the authentication module",
    options: opts
  });

  const checkpoints: Checkpoint[] = [];
  let sessionId: string | undefined;

  try {
    for await (const message of response) {
      if (message.type === "user" && message.uuid) {
        checkpoints.push({
          id: message.uuid,
          description: `After turn ${checkpoints.length + 1}`,
          timestamp: new Date()
        });
      }
      if ("session_id" in message && !sessionId) {
        sessionId = message.session_id;
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, sessionId and the checkpoints array were
    // already populated by the loop above; connection or process failures
    // yield no result message.
    console.error(`Session ended with an error: ${error}`);
  }

  // Later: rewind to any checkpoint by resuming the session
  if (checkpoints.length > 0 && sessionId) {
    const target = checkpoints[0]; // Pick any checkpoint
    const rewindQuery = query({
      prompt: "", // Empty prompt to open the connection
      options: { ...opts, resume: sessionId }
    });

    for await (const msg of rewindQuery) {
      await rewindQuery.rewindFiles(target.id);
      break;
    }
    console.log(`Rewound to: ${target.description}`);
  }
}

main();
```


[​](#try-it-out)

Try it out

This complete example creates a small utility file, has the agent add documentation comments, shows you the changes, then asks if you want to rewind. Before you begin, make sure you have the [Claude Agent SDK installed](agent-sdk-quickstart.md).

1

Create a test file

Create a new file called `utils.py` (Python) or `utils.ts` (TypeScript) and paste the following code:

utils.py

utils.ts

```python
def add(a, b):
    return a + b


def subtract(a, b):
    return a - b


def multiply(a, b):
    return a * b


def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
```

```python
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}
```

2

Run the interactive example

Create a new file called `try_checkpointing.py` (Python) or `try_checkpointing.ts` (TypeScript) in the same directory as your utility file, and paste the following code.This script asks Claude to add doc comments to your utility file, then gives you the option to rewind and restore the original.

try_checkpointing.py

try_checkpointing.ts

```python
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    UserMessage,
    ResultMessage,
)


async def main():
    # Configure the SDK with checkpointing enabled
    # - enable_file_checkpointing: Track file changes for rewinding
    # - permission_mode: Auto-accept file edits without prompting
    # - extra_args: Required to receive user message UUIDs in the stream
    options = ClaudeAgentOptions(
        enable_file_checkpointing=True,
        permission_mode="acceptEdits",
        extra_args={"replay-user-messages": None},
    )

    checkpoint_id = None  # Store the user message UUID for rewinding
    session_id = None  # Store the session ID for resuming

    print("Running agent to add doc comments to utils.py...\n")

    # Run the agent and capture checkpoint data from the response stream
    async with ClaudeSDKClient(options) as client:
        await client.query("Add doc comments to utils.py")

        async for message in client.receive_response():
            # Capture the first user message UUID - this is our restore point
            if isinstance(message, UserMessage) and message.uuid and not checkpoint_id:
                checkpoint_id = message.uuid
            # Capture the session ID so we can resume later
            if isinstance(message, ResultMessage):
                session_id = message.session_id

    print("Done! Open utils.py to see the added doc comments.\n")

    # Ask the user if they want to rewind the changes
    if checkpoint_id and session_id:
        response = input("Rewind to remove the doc comments? (y/n): ")

        if response.lower() == "y":
            # Resume the session with an empty prompt, then rewind
            async with ClaudeSDKClient(
                ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)
            ) as client:
                await client.query("")  # Empty prompt opens the connection
                async for message in client.receive_response():
                    await client.rewind_files(checkpoint_id)  # Restore files
                    break

            print(
                "\n✓ File restored! Open utils.py to verify the doc comments are gone."
            )
        else:
            print("\nKept the modified file.")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";
import * as readline from "readline";

async function main() {
  // Configure the SDK with checkpointing enabled
  // - enableFileCheckpointing: Track file changes for rewinding
  // - permissionMode: Auto-accept file edits without prompting
  // - extraArgs: Required to receive user message UUIDs in the stream
  const opts = {
    enableFileCheckpointing: true,
    permissionMode: "acceptEdits" as const,
    extraArgs: { "replay-user-messages": null }
  };

  let sessionId: string | undefined; // Store the session ID for resuming
  let checkpointId: string | undefined; // Store the user message UUID for rewinding

  console.log("Running agent to add doc comments to utils.ts...\n");

  // Run the agent and capture checkpoint data from the response stream
  const response = query({
    prompt: "Add doc comments to utils.ts",
    options: opts
  });

  try {
    for await (const message of response) {
      // Capture the first user message UUID - this is our restore point
      if (message.type === "user" && message.uuid && !checkpointId) {
        checkpointId = message.uuid;
      }
      // Capture the session ID so we can resume later
      if ("session_id" in message) {
        sessionId = message.session_id;
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, checkpointId and sessionId were already
    // captured by the loop above; connection or process failures yield no
    // result message.
    console.error(`Session ended with an error: ${error}`);
  }

  console.log("Done! Open utils.ts to see the added doc comments.\n");

  // Ask the user if they want to rewind the changes
  if (checkpointId && sessionId) {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });

    const answer = await new Promise<string>((resolve) => {
      rl.question("Rewind to remove the doc comments? (y/n): ", resolve);
    });
    rl.close();

    if (answer.toLowerCase() === "y") {
      // Resume the session with an empty prompt, then rewind
      const rewindQuery = query({
        prompt: "", // Empty prompt opens the connection
        options: { ...opts, resume: sessionId }
      });

      for await (const msg of rewindQuery) {
        await rewindQuery.rewindFiles(checkpointId); // Restore files
        break;
      }

      console.log("\n✓ File restored! Open utils.ts to verify the doc comments are gone.");
    } else {
      console.log("\nKept the modified file.");
    }
  }
}

main();
```

3

Run the example

Run the script from the same directory as your utility file.

Open your utility file (`utils.py` or `utils.ts`) in your IDE or editor before running the script. You’ll see the file update in real-time as the agent adds doc comments, then revert back to the original when you choose to rewind.

- Python

- TypeScript

```python
python try_checkpointing.py
```

```python
npx tsx try_checkpointing.ts
```

You’ll see the agent add doc comments, then a prompt asking if you want to rewind. If you choose yes, the file is restored to its original state.


[​](#limitations)

Limitations

File checkpointing has the following limitations:

| Limitation                         | Description                                                                                                                                                                           |
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Write/Edit/NotebookEdit tools only | Changes made through Bash commands are not tracked                                                                                                                                    |
| Subagent edits                     | Edits a [subagent](agent-sdk-subagents.md) applies aren’t tracked or restored, except a skill with `context: fork` running in the foreground; use git to revert untracked edits |
| Same session                       | Checkpoints are tied to the session that created them                                                                                                                                 |
| File content only                  | Creating, moving, or deleting directories is not undone by rewinding                                                                                                                  |
| Local files                        | Remote or network files are not tracked                                                                                                                                               |


[​](#troubleshooting)

Troubleshooting


[​](#checkpointing-options-not-recognized)

Checkpointing options not recognized

If `enableFileCheckpointing` or `rewindFiles()` isn’t available, you may be on an older SDK version. **Solution**: Update to the latest SDK version:

- **Python**: `pip install --upgrade claude-agent-sdk`
- **TypeScript**: `npm install @anthropic-ai/claude-agent-sdk@latest`


[​](#user-messages-don’t-have-uuids)

User messages don’t have UUIDs

If `message.uuid` is `undefined` or missing, you’re not receiving checkpoint UUIDs. **Cause**: The `replay-user-messages` option isn’t set. **Solution**: Add `extra_args={"replay-user-messages": None}` (Python) or `extraArgs: { 'replay-user-messages': null }` (TypeScript) to your options.


[​](#”no-file-checkpoint-found-for-this-message”-error)

”No file checkpoint found for this message” error

This error occurs when the checkpoint data doesn’t exist for the specified user message UUID. **Common causes**:

- File checkpointing was not enabled on the original session (`enable_file_checkpointing` or `enableFileCheckpointing` was not set to `true`)
- The session wasn’t properly completed before attempting to resume and rewind

**Solution**: Ensure `enable_file_checkpointing=True` (Python) or `enableFileCheckpointing: true` (TypeScript) was set on the original session, then use the pattern shown in the examples: capture the first user message UUID, complete the session fully, then resume with an empty prompt and call `rewindFiles()` once.


[​](#”file-rewinding-is-not-enabled”-error)

”File rewinding is not enabled” error

This error occurs when you attempt a non-interactive rewind without checkpointing enabled: running bare `claude -p` with `--rewind-files`, or running an SDK session, including a resumed one, whose options don’t enable checkpointing. The SDK sets the `CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING` environment variable internally only when `enable_file_checkpointing` (Python) or `enableFileCheckpointing` (TypeScript) is enabled on the session performing the rewind; the bare CLI never sets it. **Solution**: For the bare CLI, set the environment variable when running the command:

```python
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true claude -p --resume <session-id> --rewind-files <checkpoint-uuid>
```

For the SDK, set `enable_file_checkpointing=True` (Python) or `enableFileCheckpointing: true` (TypeScript) on the resumed session, as the examples on this page do.


[​](#”processtransport-is-not-ready-for-writing”-error)

”ProcessTransport is not ready for writing” error

This error occurs when you call `rewindFiles()` or `rewind_files()` after you’ve finished iterating through the response. The connection to the CLI process closes when the loop completes. **Solution**: Resume the session with an empty prompt, then call rewind on the new query. The lead example under [Implement checkpointing](#implement-checkpointing) shows the resume-and-rewind call in both languages, at the comment marked Step 3.


[​](#next-steps)

Next steps

- **[Sessions](agent-sdk-sessions.md)**: learn how to resume sessions, which is required for rewinding after the stream completes. Covers session IDs, resuming conversations, and session forking.
- **[Permissions](agent-sdk-permissions.md)**: configure which tools Claude can use and how file modifications are approved. Useful if you want more control over when edits happen.
- **[TypeScript SDK reference](agent-sdk-typescript.md)**: complete API reference including all options for `query()` and the `rewindFiles()` method.
- **[Python SDK reference](agent-sdk-python.md)**: complete API reference including all options for `ClaudeAgentOptions` and the `rewind_files()` method.

---

## Intercept and control agent behavior with hooks - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/hooks
Path: 05-Agent-SDK/agent-sdk-hooks.md

## On this page

- [How hooks work](#how-hooks-work)
- [Available hooks](#available-hooks)
- [Configure hooks](#configure-hooks)
  - [Matchers](#matchers)
  - [Callback functions](#callback-functions)
  - [Inputs](#inputs)
  - [Outputs](#outputs)
  - [Asynchronous output](#asynchronous-output)
- [Examples](#examples)
  - [Modify tool input](#modify-tool-input)
  - [Add context and block a tool](#add-context-and-block-a-tool)
  - [Auto-approve specific tools](#auto-approve-specific-tools)
  - [Register multiple hooks](#register-multiple-hooks)
  - [Filter with multi-tool matchers](#filter-with-multi-tool-matchers)
  - [Track subagent activity](#track-subagent-activity)
  - [Make HTTP requests from hooks](#make-http-requests-from-hooks)
  - [Forward notifications to Slack](#forward-notifications-to-slack)
- [Fix common issues](#fix-common-issues)
  - [Hook not firing](#hook-not-firing)
  - [Matcher not filtering as expected](#matcher-not-filtering-as-expected)
  - [Hook timeout](#hook-timeout)
  - [Tool blocked unexpectedly](#tool-blocked-unexpectedly)
  - [Modified input not applied](#modified-input-not-applied)
  - [Session hooks not available in Python](#session-hooks-not-available-in-python)
  - [Subagent permission prompts multiplying](#subagent-permission-prompts-multiplying)
  - [Recursive hook loops with subagents](#recursive-hook-loops-with-subagents)
  - [systemMessage not appearing in output](#systemmessage-not-appearing-in-output)
- [Related resources](#related-resources)

Control and observability

# Intercept and control agent behavior with hooks

Copy pageCopy page

Intercept and customize agent behavior at key execution points with hooks

Copy pageCopy page

Hooks are callback functions that run your code in response to agent events, like a tool being called, a session starting, or execution stopping. With hooks, you can:

- **Block dangerous operations** before they execute, like destructive shell commands or unauthorized file access
- **Log and audit** every tool call for compliance, debugging, or analytics
- **Transform inputs and outputs** to sanitize data, inject credentials, or redirect file paths
- **Require human approval** for sensitive actions like database writes or API calls
- **Track session lifecycle** to manage state, clean up resources, or send notifications


[​](#how-hooks-work)

How hooks work

1

An event fires

Something happens during agent execution and the SDK fires an event: a tool is about to be called (`PreToolUse`), a tool returned a result (`PostToolUse`), a subagent started or stopped, the agent is idle, or execution finished. See the [full list of events](#available-hooks).

2

The SDK collects registered hooks

The SDK checks for hooks registered for that event type. This includes callback hooks you pass in `options.hooks` and shell command hooks from settings files when the corresponding [`settingSources`](agent-sdk-typescript.md#settingsource) or [`setting_sources`](agent-sdk-python.md#settingsource) entry is enabled, which it is for default `query()` options.

3

Matchers filter which hooks run

If a hook has a [`matcher`](#matchers) pattern (like `"Write|Edit"`), the SDK tests it against the event’s target (for example, the tool name). Hooks without a matcher run for every event of that type.

4

Callback functions execute

Each matching hook’s [callback function](#callback-functions) receives input about what’s happening: the tool name, its arguments, the session ID, and other event-specific details.

5

Your callback returns a decision

After performing any operations (logging, API calls, validation), your callback returns an [output object](#outputs) that tells the agent what to do: allow the operation, block it, modify the input, or inject context into the conversation.

The following example puts these steps together. It registers a `PreToolUse` hook (step 1) with a `"Write|Edit"` matcher (step 3) so the callback only fires for file-writing tools. When triggered, the callback receives the tool’s input (step 4), checks if the file path targets a `.env` file, and returns `permissionDecision: "deny"` to block the operation (step 5):

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import (
    AssistantMessage,
    ClaudeSDKClient,
    ClaudeAgentOptions,
    HookMatcher,
    ResultMessage,
)


# Define a hook callback that receives tool call details
async def protect_env_files(input_data, tool_use_id, context):
    # Extract the file path from the tool's input arguments
    file_path = input_data["tool_input"].get("file_path", "")
    file_name = file_path.split("/")[-1]

    # Block the operation if targeting a .env file
    if file_name == ".env":
        return {
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "deny",
                "permissionDecisionReason": "Cannot modify .env files",
            }
        }

    # Return empty object to allow the operation
    return {}


async def main():
    options = ClaudeAgentOptions(
        hooks={
            # Register the hook for PreToolUse events
            # The matcher filters to only Write and Edit tool calls
            "PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
        }
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Create a .env file with the standard local development database configuration")
        async for message in client.receive_response():
            # Filter for assistant and result messages
            if isinstance(message, (AssistantMessage, ResultMessage)):
                print(message)


asyncio.run(main())
```

```python
import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

// Define a hook callback with the HookCallback type
const protectEnvFiles: HookCallback = async (input, toolUseID, { signal }) => {
  // Cast input to the specific hook type for type safety
  const preInput = input as PreToolUseHookInput;

  // Cast tool_input to access its properties (typed as unknown in the SDK)
  const toolInput = preInput.tool_input as Record<string, unknown>;
  const filePath = toolInput?.file_path as string;
  const fileName = filePath?.split("/").pop();

  // Block the operation if targeting a .env file
  if (fileName === ".env") {
    return {
      hookSpecificOutput: {
        hookEventName: preInput.hook_event_name,
        permissionDecision: "deny",
        permissionDecisionReason: "Cannot modify .env files"
      }
    };
  }

  // Return empty object to allow the operation
  return {};
};

for await (const message of query({
  prompt: "Create a .env file with the standard local development database configuration",
  options: {
    hooks: {
      // Register the hook for PreToolUse events
      // The matcher filters to only Write and Edit tool calls
      PreToolUse: [{ matcher: "Write|Edit", hooks: [protectEnvFiles] }]
    }
  }
})) {
  // Filter for assistant and result messages
  if (message.type === "assistant" || message.type === "result") {
    console.log(message);
  }
}
```

When you run either script, Claude attempts to create the `.env` file and the hook denies the tool call.


[​](#available-hooks)

Available hooks

The SDK provides hooks for different stages of agent execution. Some hooks are available in both SDKs, while others are TypeScript-only.

| Hook Event                                                  | Python SDK | TypeScript SDK | What triggers it                                                                                                                        | Example use case                                                                                                                                               |
|-------------------------------------------------------------|------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `PreToolUse`                                                | Yes        | Yes            | Tool call request (can block or modify)                                                                                                 | Block dangerous shell commands                                                                                                                                 |
| `PostToolUse`                                               | Yes        | Yes            | Tool execution result                                                                                                                   | Log all file changes to audit trail                                                                                                                            |
| `PostToolUseFailure`                                        | Yes        | Yes            | Tool execution failure                                                                                                                  | Handle or log tool errors                                                                                                                                      |
| `PostToolBatch`                                             | No         | Yes            | A full batch of tool calls resolves, once per batch before the next model call                                                          | Inject conventions once for the whole batch                                                                                                                    |
| [`UserPromptSubmit`](../07-Hooks/hooks.md#userpromptsubmit)       | Yes        | Yes            | A prompt is submitted, including a turn Claude Code starts on its own                                                                   | Inject additional context into prompts                                                                                                                         |
| [`UserPromptExpansion`](../07-Hooks/hooks.md#userpromptexpansion) | No         | Yes            | A user-typed command, or an MCP prompt, expands into a prompt before it reaches Claude. Doesn’t fire when Claude invokes a skill itself | Block a command from direct invocation or add context when a skill is typed                                                                                    |
| `MessageDisplay`                                            | No         | Yes            | An assistant message with text completes, once per message with the full message text                                                   | Redact or reformat the displayed text without changing the transcript                                                                                          |
| `Stop`                                                      | Yes        | Yes            | Agent execution stop                                                                                                                    | Save session state before exit                                                                                                                                 |
| `StopFailure`                                               | No         | Yes            | The turn ends with an API error instead of a normal stop                                                                                | Log failures or send alerts                                                                                                                                    |
| `SubagentStart`                                             | Yes        | Yes            | Subagent initialization                                                                                                                 | Track parallel task spawning                                                                                                                                   |
| `SubagentStop`                                              | Yes        | Yes            | Subagent completion                                                                                                                     | Aggregate results from parallel tasks                                                                                                                          |
| `PreCompact`                                                | Yes        | Yes            | Conversation compaction request                                                                                                         | Archive full transcript before summarizing                                                                                                                     |
| `PostCompact`                                               | No         | Yes            | Conversation compaction completes                                                                                                       | Log the generated summary                                                                                                                                      |
| [`PreModelSwitch`](../07-Hooks/hooks.md#premodelswitch)           | No         | Yes            | A requested model switch, before it happens (can block)                                                                                 | Block switching to a specific model                                                                                                                            |
| [`PostModelSwitch`](../07-Hooks/hooks.md#postmodelswitch)         | No         | Yes            | The session’s model changes, including an automatic fallback                                                                            | Give Claude model-specific guidance for the new model                                                                                                          |
| `PermissionRequest`                                         | Yes        | Yes            | A tool call needs a permission decision                                                                                                 | Custom permission handling                                                                                                                                     |
| `PermissionDenied`                                          | No         | Yes            | Auto mode denies a tool call, including denials without a classifier verdict                                                            | Log denials, or tell the model it may retry; Claude Code ignores `retry: true` for no-verdict denials. See [PermissionDenied](../07-Hooks/hooks.md#permissiondenied) |
| `SessionStart`                                              | No         | Yes            | Session initialization                                                                                                                  | Initialize logging and telemetry                                                                                                                               |
| `SessionEnd`                                                | No         | Yes            | Session termination                                                                                                                     | Clean up temporary resources                                                                                                                                   |
| `Notification`                                              | Yes        | Yes            | Agent status messages                                                                                                                   | Send agent status updates to Slack or PagerDuty                                                                                                                |
| `Setup`                                                     | No         | Yes            | Session setup/maintenance                                                                                                               | Run initialization tasks                                                                                                                                       |
| `TeammateIdle`                                              | No         | Yes            | Teammate becomes idle                                                                                                                   | Reassign work or notify                                                                                                                                        |
| `TaskCreated`                                               | No         | Yes            | A task is created via the `TaskCreate` tool                                                                                             | Enforce task naming conventions                                                                                                                                |
| [`TaskCompleted`](../07-Hooks/hooks.md#taskcompleted)             | No         | Yes            | A task is marked completed                                                                                                              | Require passing tests before a task closes                                                                                                                     |
| `Elicitation`                                               | No         | Yes            | An MCP server requests user input mid-task                                                                                              | Respond to MCP input requests programmatically                                                                                                                 |
| `ElicitationResult`                                         | No         | Yes            | A user responds to an MCP elicitation                                                                                                   | Modify or block the response before it returns to the server                                                                                                   |
| `ConfigChange`                                              | No         | Yes            | Configuration file changes                                                                                                              | Reload settings dynamically                                                                                                                                    |
| `InstructionsLoaded`                                        | No         | Yes            | A `CLAUDE.md` or rules file is loaded into context                                                                                      | Audit which instruction files load                                                                                                                             |
| `WorktreeCreate`                                            | No         | Yes            | Git worktree created                                                                                                                    | Track isolated workspaces                                                                                                                                      |
| `WorktreeRemove`                                            | No         | Yes            | A worktree created by a `WorktreeCreate` hook is being removed                                                                          | Clean up workspace resources                                                                                                                                   |
| `CwdChanged`                                                | No         | Yes            | The working directory changes during a session                                                                                          | Reload environment variables per directory                                                                                                                     |
| `FileChanged`                                               | No         | Yes            | A watched file is modified, created, or deleted                                                                                         | Reload configuration when project files change                                                                                                                 |
| `DirectoryAdded`                                            | No         | Yes            | A working directory is added during a session                                                                                           | Install dependencies for a repository added mid-session                                                                                                        |


[​](#configure-hooks)

Configure hooks

To configure a hook, pass it in the `hooks` field of your agent options (`ClaudeAgentOptions` in Python, the `options` object in TypeScript). This snippet assumes you have already defined a hook callback, like `protect_env_files` in Python or `protectEnvFiles` in TypeScript from the example above:

Python

TypeScript

```python
options = ClaudeAgentOptions(
    hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
)

async with ClaudeSDKClient(options=options) as client:
    await client.query("Your prompt")
    async for message in client.receive_response():
        print(message)
```

```python
for await (const message of query({
  prompt: "Your prompt",
  options: {
    hooks: {
      PreToolUse: [{ matcher: "Bash", hooks: [myCallback] }]
    }
  }
})) {
  console.log(message);
}
```

The `hooks` option is a dictionary in Python or an object in TypeScript, where:

- **Keys**: [hook event names](#available-hooks) such as `'PreToolUse'`, `'PostToolUse'`, and `'Stop'`
- **Values**: arrays of [matchers](#matchers), each containing an optional filter pattern and your [callback functions](#callback-functions)


[​](#matchers)

Matchers

Use matchers to filter when your callbacks fire. The `matcher` field matches against a different value depending on the hook event type. For example, tool-based hooks match against the tool name, while `Notification` hooks match against the notification type. SDK matchers follow the same rules as [matchers in settings files](../07-Hooks/hooks.md#matcher-patterns). That section documents the exact-string and regular-expression evaluation paths, their version requirements, and the matcher values for each event type.

| Option    | Type             | Default     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|-----------|------------------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `matcher` | `string`         | `undefined` | Pattern matched against the event’s filter field, following the [rules for matchers in settings files](../07-Hooks/hooks.md#matcher-patterns). For tool hooks, this is the tool name. Built-in tools include `Bash`, `Read`, `Write`, `Edit`, `Glob`, `Grep`, `WebFetch`, `Agent`, and others (see [Tool Input Types](agent-sdk-typescript.md#tool-input-types) for the full list). MCP tools use the pattern `mcp__<server>__<action>`, where `<server>` is the key you use in the `mcpServers` configuration. |
| `hooks`   | `HookCallback[]` | \-          | Required. Array of callback functions to execute when the pattern matches                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `timeout` | `number`         | `undefined` | Timeout in seconds. When omitted, Claude Code applies the [event’s default timeout](#hook-timeout). Your SDK callbacks follow the `command` hook defaults                                                                                                                                                                                                                                                                                                                                                       |

Use the `matcher` pattern to target specific tools whenever possible. A matcher with `'Bash'` only runs for Bash commands, while omitting the pattern runs your callbacks for every occurrence of the event. Omit it on purpose to log every tool call your session makes.


[​](#callback-functions)

Callback functions


[​](#inputs)

Inputs

Every hook callback receives three arguments:

- **Input data:** a typed object containing event details. Each hook type has its own input shape. For example, `PreToolUseHookInput` includes `tool_name` and `tool_input`, while `NotificationHookInput` includes `message`. See the full type definitions in the [TypeScript](agent-sdk-typescript.md#hookinput) and [Python](agent-sdk-python.md#hookinput) SDK references.
  - All hook inputs share `session_id`, `cwd`, and `hook_event_name`.
  - `agent_id` and `agent_type` are populated when the hook fires inside a subagent. In TypeScript, these are on the base hook input and available to all hook types. In Python, they are optional fields on `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, and `PermissionRequest`, and required fields on `SubagentStart` and `SubagentStop`.
- **Tool use ID** (`str | None` / `string | undefined`): correlates `PreToolUse` and `PostToolUse` events for the same tool call.
- **Context:** in TypeScript, contains a `signal` property (`AbortSignal`) for cancellation. In Python, this argument is reserved for future use.


[​](#outputs)

Outputs

Your callback returns an object with two categories of fields:

- **Top-level fields** are accepted on every event: `systemMessage` shows a message to the user, and `continue` (`continue_` in Python) determines whether the agent keeps running after this hook. Some events discard them or deliver them elsewhere. Each [event’s section](../07-Hooks/hooks.md#hook-events) on the hooks page says where they land.
- **`hookSpecificOutput`** controls the current operation. The fields you set inside depend on the hook event type:
  - For `PreToolUse` hooks, this is where you set `permissionDecision` (`"allow"`, `"deny"`, `"ask"`, or `"defer"`), `permissionDecisionReason`, and `updatedInput`. If you return `"defer"`, the turn ends with a result message whose `stop_reason` is `"tool_deferred"`, so you can [resume the call later](../07-Hooks/hooks.md#defer-a-tool-call-for-later).
  - For `PostToolUse` hooks, you can set `additionalContext` to append information to the tool result. To replace the tool’s output before Claude sees it, set `updatedToolOutput`, which works for any tool in both SDKs. The older `updatedMCPToolOutput` field replaces MCP tool output only.
  - In the TypeScript SDK, a `PostToolUse` callback can also return `classifierContext`, a short note about the tool call’s result for the [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) permission classifier. Because your callback runs in your application’s own process, the classifier may weigh a user statement you relay in the note as user intent. The field requires TypeScript Agent SDK v0.3.236 or later. [Annotate a result for the auto mode classifier](../07-Hooks/hooks.md#annotate-a-result-for-the-auto-mode-classifier) covers the length cap, the synchronous-only rule, and what not to put in the note.

Return `{}` to allow the operation without changes. SDK callback hooks use the same JSON output format as [Claude Code shell command hooks](../07-Hooks/hooks.md#json-output), which documents every field and event-specific option. For the SDK type definitions, see the [TypeScript](agent-sdk-typescript.md#synchookjsonoutput) and [Python](agent-sdk-python.md#synchookjsonoutput) SDK references.

When multiple hooks or permission rules apply, `deny` takes priority over `defer`, which takes priority over `ask`, which takes priority over `allow`. If any hook returns `deny`, the operation is blocked regardless of other hooks.


[​](#asynchronous-output)

Asynchronous output

By default, the agent waits for your hook to return before proceeding. If your hook performs a side effect, such as logging or sending a webhook, and doesn’t need to influence the agent’s behavior, you can return an async output instead. This tells the agent to continue immediately without waiting for the hook to finish. In this snippet, `send_to_logging_service` in Python and `sendToLoggingService` in TypeScript stand in for any logging function you define:

Python

TypeScript

```python
async def async_hook(input_data, tool_use_id, context):
    # Start a background task, then return immediately
    asyncio.create_task(send_to_logging_service(input_data))
    return {"async_": True, "asyncTimeout": 30000}
```

```python
const asyncHook: HookCallback = async (input, toolUseID, { signal }) => {
  // Start a background task, then return immediately
  sendToLoggingService(input).catch(console.error);
  return { async: true, asyncTimeout: 30000 };
};
```

| Field          | Type     | Description                                                                                                    |
|----------------|----------|----------------------------------------------------------------------------------------------------------------|
| `async`        | `true`   | Signals async mode. The agent proceeds without waiting. In Python, use `async_` to avoid the reserved keyword. |
| `asyncTimeout` | `number` | Optional timeout in milliseconds for the background operation                                                  |

Async outputs can’t block, modify, or inject context into the operation since the agent has already moved on. Use them only for side effects like logging, metrics, or notifications.


[​](#examples)

Examples

Several examples in this section show only the callback function. To run one, register the callback under the matching event in the `hooks` field of your options, as shown in [Configure hooks](#configure-hooks).


[​](#modify-tool-input)

Modify tool input

This example intercepts Write tool calls and rewrites the `file_path` argument to prepend `/sandbox`, redirecting all file writes to a sandboxed directory. The callback returns `updatedInput` with the modified path and `permissionDecision: 'allow'` to auto-approve the rewritten operation:

Python

TypeScript

```python
async def redirect_to_sandbox(input_data, tool_use_id, context):
    if input_data["hook_event_name"] != "PreToolUse":
        return {}

    if input_data["tool_name"] == "Write":
        original_path = input_data["tool_input"].get("file_path", "")
        return {
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "allow",
                "updatedInput": {
                    **input_data["tool_input"],
                    "file_path": f"/sandbox{original_path}",
                },
            }
        }
    return {}
```

```python
const redirectToSandbox: HookCallback = async (input, toolUseID, { signal }) => {
  if (input.hook_event_name !== "PreToolUse") return {};

  const preInput = input as PreToolUseHookInput;
  const toolInput = preInput.tool_input as Record<string, unknown>;
  if (preInput.tool_name === "Write") {
    const originalPath = toolInput.file_path as string;
    return {
      hookSpecificOutput: {
        hookEventName: preInput.hook_event_name,
        permissionDecision: "allow",
        updatedInput: {
          ...toolInput,
          file_path: `/sandbox${originalPath}`
        }
      }
    };
  }
  return {};
};
```

Pair `updatedInput` with `permissionDecision: 'allow'` to auto-approve the modified input, or `permissionDecision: 'ask'` to show it to the user. If you omit `permissionDecision`, the modified input still applies and flows through the normal permission evaluation. With `'defer'`, `updatedInput` is ignored. Always return a new object rather than mutating the original `tool_input`.

To confirm the redirect, set the prefix to a path you can write to, such as `./sandbox` or `/tmp/sandbox` (macOS doesn’t allow creating a root-level `/sandbox` directory), then ask the agent to write a file: the Write tool’s result in the message stream names the path with your sandbox prefix rather than the one Claude requested.


[​](#add-context-and-block-a-tool)

Add context and block a tool

This example blocks writes to the `/etc` directory and explains why to both the model and the user:

- `permissionDecision: 'deny'` stops the tool call.
- `permissionDecisionReason` tells the model why, so it avoids retrying.
- `systemMessage` shows the user what happened.

Python

TypeScript

```python
async def block_etc_writes(input_data, tool_use_id, context):
    file_path = input_data["tool_input"].get("file_path", "")

    if file_path.startswith("/etc"):
        return {
            # Top-level field: message shown to the user
            "systemMessage": "Remember: system directories like /etc are protected.",
            # hookSpecificOutput: block the operation
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "deny",
                "permissionDecisionReason": "Writing to /etc is not allowed",
            },
        }
    return {}
```

```python
const blockEtcWrites: HookCallback = async (input, toolUseID, { signal }) => {
  const preInput = input as PreToolUseHookInput;
  const toolInput = preInput.tool_input as Record<string, unknown>;
  const filePath = toolInput?.file_path as string;

  if (filePath?.startsWith("/etc")) {
    return {
      // Top-level field: message shown to the user
      systemMessage: "Remember: system directories like /etc are protected.",
      // hookSpecificOutput: block the operation
      hookSpecificOutput: {
        hookEventName: preInput.hook_event_name,
        permissionDecision: "deny",
        permissionDecisionReason: "Writing to /etc is not allowed"
      }
    };
  }
  return {};
};
```

To confirm the block, register the callback under `PreToolUse` with a `Write|Edit` matcher and ask the agent to create a file under `/etc`: the Write tool’s result in the message stream contains `Writing to /etc is not allowed`, and no file is created.


[​](#auto-approve-specific-tools)

Auto-approve specific tools

By default, the agent may prompt for permission before using certain tools. This example auto-approves read-only filesystem tools (Read, Glob, Grep) by returning `permissionDecision: 'allow'`, letting them run without user confirmation while leaving all other tools subject to normal permission checks:

Python

TypeScript

```python
async def auto_approve_read_only(input_data, tool_use_id, context):
    if input_data["hook_event_name"] != "PreToolUse":
        return {}

    read_only_tools = ["Read", "Glob", "Grep"]
    if input_data["tool_name"] in read_only_tools:
        return {
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "allow",
                "permissionDecisionReason": "Read-only tool auto-approved",
            }
        }
    return {}
```

```python
const autoApproveReadOnly: HookCallback = async (input, toolUseID, { signal }) => {
  if (input.hook_event_name !== "PreToolUse") return {};

  const preInput = input as PreToolUseHookInput;
  const readOnlyTools = ["Read", "Glob", "Grep"];
  if (readOnlyTools.includes(preInput.tool_name)) {
    return {
      hookSpecificOutput: {
        hookEventName: preInput.hook_event_name,
        permissionDecision: "allow",
        permissionDecisionReason: "Read-only tool auto-approved"
      }
    };
  }
  return {};
};
```


[​](#register-multiple-hooks)

Register multiple hooks

When an event fires, all matching hooks run in parallel. For permission decisions, the most restrictive result applies: a single `deny` blocks the tool call regardless of what the other hooks return. Because completion order is non-deterministic, write each hook to act independently rather than relying on another hook having run first. The example below registers three independent checks for every tool call. The hook names in it, such as `audit_logger` in Python or `auditLogger` in TypeScript, stand in for callbacks you define:

Python

TypeScript

```python
options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(hooks=[authorization_check]),
            HookMatcher(hooks=[input_validator]),
            HookMatcher(hooks=[audit_logger]),
        ]
    }
)
```

```python
const options = {
  hooks: {
    PreToolUse: [
      { hooks: [authorizationCheck] },
      { hooks: [inputValidator] },
      { hooks: [auditLogger] }
    ]
  }
};
```


[​](#filter-with-multi-tool-matchers)

Filter with multi-tool matchers

Use multi-tool matchers to share one callback across related tools. This example registers three matchers with different scopes, and each hook it names stands in for a callback you define:

- A pipe-separated exact list (`Write|Edit|NotebookEdit`) triggers `file_security_hook` only for file modification tools.
- A regex (`^mcp__`) triggers `mcp_audit_hook` for any MCP tool whose name starts with `mcp__`.
- An omitted matcher triggers `global_logger` for every tool call regardless of name.

Python

TypeScript

```python
options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            # Match file modification tools
            HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[file_security_hook]),
            # Match all MCP tools
            HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
            # Match everything (no matcher)
            HookMatcher(hooks=[global_logger]),
        ]
    }
)
```

```python
const options = {
  hooks: {
    PreToolUse: [
      // Match file modification tools
      { matcher: "Write|Edit|NotebookEdit", hooks: [fileSecurityHook] },

      // Match all MCP tools
      { matcher: "^mcp__", hooks: [mcpAuditHook] },

      // Match everything (no matcher)
      { hooks: [globalLogger] }
    ]
  }
};
```


[​](#track-subagent-activity)

Track subagent activity

Use `SubagentStop` hooks to monitor when subagents finish their work. See the full input type in the [TypeScript](agent-sdk-typescript.md#hookinput) and [Python](agent-sdk-python.md#hookinput) SDK references. This example logs a summary each time a subagent completes:

Python

TypeScript

```python
async def subagent_tracker(input_data, tool_use_id, context):
    # Log subagent details when it finishes
    print(f"[SUBAGENT] Completed: {input_data['agent_id']}")
    print(f"  Transcript: {input_data['agent_transcript_path']}")
    print(f"  Tool use ID: {tool_use_id}")
    print(f"  Stop hook active: {input_data.get('stop_hook_active')}")
    return {}


options = ClaudeAgentOptions(
    hooks={"SubagentStop": [HookMatcher(hooks=[subagent_tracker])]}
)
```

```python
import { HookCallback, SubagentStopHookInput } from "@anthropic-ai/claude-agent-sdk";

const subagentTracker: HookCallback = async (input, toolUseID, { signal }) => {
  // Cast to SubagentStopHookInput to access subagent-specific fields
  const subInput = input as SubagentStopHookInput;

  // Log subagent details when it finishes
  console.log(`[SUBAGENT] Completed: ${subInput.agent_id}`);
  console.log(`  Transcript: ${subInput.agent_transcript_path}`);
  console.log(`  Tool use ID: ${toolUseID}`);
  console.log(`  Stop hook active: ${subInput.stop_hook_active}`);
  return {};
};

const options = {
  hooks: {
    SubagentStop: [{ hooks: [subagentTracker] }]
  }
};
```

To confirm the hook fires, register the callback and ask the agent to delegate a small task to a subagent, such as listing the files in the current directory: when the subagent finishes, the callback prints the `[SUBAGENT] Completed:` lines with the subagent’s ID and transcript path.


[​](#make-http-requests-from-hooks)

Make HTTP requests from hooks

Hooks can perform asynchronous operations like HTTP requests. Catch errors inside your hook instead of letting them propagate. This example sends a webhook after each tool completes, logging which tool ran and when. The hook catches errors from a failed webhook:

Python

TypeScript

```python
import asyncio
import json
import urllib.request
from datetime import datetime


def _send_webhook(tool_name):
    """Synchronous helper that POSTs tool usage data to an external webhook."""
    data = json.dumps(
        {
            "tool": tool_name,
            "timestamp": datetime.now().isoformat(),
        }
    ).encode()
    req = urllib.request.Request(
        "https://api.example.com/webhook",
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    urllib.request.urlopen(req)


async def webhook_notifier(input_data, tool_use_id, context):
    # Only fire after a tool completes (PostToolUse), not before
    if input_data["hook_event_name"] != "PostToolUse":
        return {}

    try:
        # Run the blocking HTTP call in a thread to avoid blocking the event loop
        await asyncio.to_thread(_send_webhook, input_data["tool_name"])
    except Exception as e:
        # Log the error but don't raise
        print(f"Webhook request failed: {e}")

    return {}
```

```python
import { query, HookCallback, PostToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

const webhookNotifier: HookCallback = async (input, toolUseID, { signal }) => {
  // Only fire after a tool completes (PostToolUse), not before
  if (input.hook_event_name !== "PostToolUse") return {};

  try {
    await fetch("https://api.example.com/webhook", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        tool: (input as PostToolUseHookInput).tool_name,
        timestamp: new Date().toISOString()
      }),
      // Pass signal so the request cancels if the hook times out
      signal
    });
  } catch (error) {
    // Handle cancellation separately from other errors
    if (error instanceof Error && error.name === "AbortError") {
      console.log("Webhook request cancelled");
    }
    // Don't re-throw
  }

  return {};
};

// Register as a PostToolUse hook
for await (const message of query({
  prompt: "Refactor the auth module",
  options: {
    hooks: {
      PostToolUse: [{ hooks: [webhookNotifier] }]
    }
  }
})) {
  console.log(message);
}
```

To confirm the hook fires, point the webhook URL at an endpoint you can watch and send a prompt that uses a tool: the hook sends a POST with the tool name and timestamp after each tool completes.


[​](#forward-notifications-to-slack)

Forward notifications to Slack

Use `Notification` hooks to receive system notifications from the agent and forward them to external services. In SDK sessions, Claude Code runs this hook for the following notification types:

- [`permission_prompt`](../07-Hooks/hooks.md#notification) once a permission request has waited about six seconds on your [`canUseTool` callback](agent-sdk-user-input.md). Requires TypeScript Agent SDK v0.3.233 or later, or Python Agent SDK v0.2.139 or later
- `elicitation_complete` and `elicitation_response` for user-prompt elicitation flows

Claude Code emits the other types, such as `idle_prompt`, `auth_success`, and `elicitation_dialog`, from interactive UI that SDK sessions don’t run. Each notification includes a `message` field with a human-readable description and optionally a `title`. This example forwards every notification to a Slack channel. It requires a [Slack incoming webhook URL](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/), which you create by adding an app to your Slack workspace and enabling incoming webhooks:

Python

TypeScript

```python
import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
    """Synchronous helper that sends a message to Slack via incoming webhook."""
    data = json.dumps({"text": f"Agent status: {message}"}).encode()
    req = urllib.request.Request(
        "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    urllib.request.urlopen(req)


async def notification_handler(input_data, tool_use_id, context):
    try:
        # Run the blocking HTTP call in a thread to avoid blocking the event loop
        await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
    except Exception as e:
        print(f"Failed to send notification: {e}")

    # Return empty object. Notification hooks don't modify agent behavior
    return {}


async def main():
    options = ClaudeAgentOptions(
        hooks={
            # Register the hook for Notification events (no matcher needed)
            "Notification": [HookMatcher(hooks=[notification_handler])],
        },
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Analyze this codebase")
        async for message in client.receive_response():
            print(message)


asyncio.run(main())
```

```python
import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";

// Define a hook callback that sends notifications to Slack
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
  // Cast to NotificationHookInput to access the message field
  const notification = input as NotificationHookInput;

  try {
    // POST the notification message to a Slack incoming webhook
    await fetch("https://hooks.slack.com/services/YOUR/WEBHOOK/URL", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        text: `Agent status: ${notification.message}`
      }),
      // Pass signal so the request cancels if the hook times out
      signal
    });
  } catch (error) {
    if (error instanceof Error && error.name === "AbortError") {
      console.log("Notification cancelled");
    } else {
      console.error("Failed to send notification:", error);
    }
  }

  // Return empty object. Notification hooks don't modify agent behavior
  return {};
};

// Register the hook for Notification events (no matcher needed)
for await (const message of query({
  prompt: "Analyze this codebase",
  options: {
    hooks: {
      Notification: [{ hooks: [notificationHandler] }]
    }
  }
})) {
  console.log(message);
}
```

When a `Notification` event fires, the hook posts the notification’s `message`, prefixed with `Agent status:`, to the channel your webhook targets.


[​](#fix-common-issues)

Fix common issues


[​](#hook-not-firing)

Hook not firing

- Verify the hook event name is correct and case-sensitive (`PreToolUse`, not `preToolUse`)
- Check that your matcher pattern matches the tool name exactly
- Ensure the hook is under the correct event type in `options.hooks`
- For non-tool hooks that support matchers, like `Notification` and `SubagentStop`, matchers match against different fields, and `Stop` ignores matchers entirely (see [matcher patterns](../07-Hooks/hooks.md#matcher-patterns))
- Hooks may not fire when the agent hits the [`max_turns`](agent-sdk-python.md#claudeagentoptions) limit because the session ends before hooks can execute


[​](#matcher-not-filtering-as-expected)

Matcher not filtering as expected

Matchers only match tool names, not file paths or other arguments. To filter by file path, check `tool_input.file_path` inside your hook:

```python
const myHook: HookCallback = async (input, toolUseID, { signal }) => {
  const preInput = input as PreToolUseHookInput;
  const toolInput = preInput.tool_input as Record<string, unknown>;
  const filePath = toolInput?.file_path as string;
  if (!filePath?.endsWith(".md")) return {}; // Skip non-markdown files
  // Process markdown files...
  return {};
};
```


[​](#hook-timeout)

Hook timeout

Claude Code runs each callback with a timeout, which you set in seconds with the `timeout` field on its `HookMatcher`. When you don’t set one, Claude Code uses the event’s default: 600 seconds for most events, 30 seconds for `UserPromptSubmit`, `PreModelSwitch`, and `PostModelSwitch`, and 10 seconds for `MessageDisplay`. Claude Code runs `SessionEnd` callbacks during shutdown under the shorter [SessionEnd timeout budget](../07-Hooks/hooks.md#sessionend-input), 1.5 seconds by default. When a callback exceeds its timeout, Claude Code cancels it and discards its output, and the session continues rather than hanging. What happens next depends on the event:

- `PreToolUse`: Claude Code doesn’t run the tool call, Claude receives a tool result stating the hook didn’t respond before its timeout, and the turn continues. If another `PreToolUse` hook returned an explicit deny, Claude receives that denial instead of the timeout error. Before v2.1.210, Claude Code reported the timeout to Claude as a user rejection, which made unattended sessions stop and wait for input.
- `PostToolUse` and `PostToolUseFailure`: Claude Code keeps the tool result and the turn continues.
- `UserPromptSubmit` and [`UserPromptExpansion`](../07-Hooks/hooks.md#userpromptexpansion): Claude Code blocks the prompt with a message naming the hook and the timeout, and the session continues. Because a callback on these events can act as a policy gate, Claude Code never lets a timed-out prompt through unscreened. Before v2.1.208, Claude Code ended the query with `error_during_execution` when a callback on these events timed out.
- `Stop` and `SubagentStop`: the timed-out callback counts as returning no decision. The agent or subagent stops as if that callback had allowed it, and a decision from your other hooks on the event still applies. Before Claude Code v2.1.273, a timed-out `Stop` or `SubagentStop` callback counted as a failed hook run, and Claude Code discarded the decisions of your other hooks on the event.
- `SessionStart`: the timed-out callback counts as returning no output, and the session continues with the output of your other `SessionStart` hooks.
- `PreModelSwitch`: Claude Code blocks the model switch. A hook that doesn’t answer hasn’t approved the switch.
- Other events, such as `Notification`, `PreCompact`, and `PostModelSwitch`: Claude Code logs the failure and continues.

The first time a `Stop` or `SessionStart` callback times out in the main session, Claude Code also adds an [`SDKInformationalMessage`](agent-sdk-typescript.md#sdkinformationalmessage) to the message stream saying the app driving the session didn’t respond. Later timeouts don’t repeat that message while your app stays unresponsive. If you interrupt the query while a callback is pending, Claude Code cancels the pending tool call. Before v2.1.208, the tool call could still proceed if you interrupted during a pending `PreToolUse` callback. If your callback needs more time, set a higher `timeout` on its `HookMatcher`. In TypeScript, use the `AbortSignal` from the third callback argument to handle cancellation gracefully when the timeout fires.


[​](#tool-blocked-unexpectedly)

Tool blocked unexpectedly

- Check all `PreToolUse` hooks for `permissionDecision: 'deny'` returns
- Add logging to your hooks to see what `permissionDecisionReason` they’re returning
- Verify matcher patterns aren’t too broad: an empty matcher matches all tools


[​](#modified-input-not-applied)

Modified input not applied

- Ensure `updatedInput` is inside `hookSpecificOutput`, not at the top level:

  ``` shiki
  return {
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "allow",
      updatedInput: { command: "new command" }
    }
  };
  ```
- Don’t pair `updatedInput` with `permissionDecision: 'defer'`, which drops the modified input. Omitting `permissionDecision` is fine: the modified input still applies through the normal permission evaluation. You can also return `'allow'` to auto-approve the modified input or `'ask'` to show it to the user for approval
- Include `hookEventName` in `hookSpecificOutput` to identify which hook type the output is for


[​](#session-hooks-not-available-in-python)

Session hooks not available in Python

`SessionStart` and `SessionEnd` can be registered as SDK callback hooks in TypeScript, but aren’t available in the Python SDK because its `HookEvent` type omits them. In Python, they are only available as [shell command hooks](../07-Hooks/hooks.md#hook-events) defined in settings files such as `.claude/settings.json`. Which settings files your SDK application loads depends on [`setting_sources`](agent-sdk-python.md#settingsource) or [`settingSources`](agent-sdk-typescript.md#settingsource). If you set that option, include the source that holds the hooks:

Python

TypeScript

```python
options = ClaudeAgentOptions(
    setting_sources=["project"],  # Loads .claude/settings.json including hooks
)
```

```python
const options = {
  settingSources: ["project"] // Loads .claude/settings.json including hooks
};
```

To run initialization logic as a Python SDK callback instead, use the first message from `client.receive_response()` as your trigger.


[​](#subagent-permission-prompts-multiplying)

Subagent permission prompts multiplying

When spawning multiple subagents, each one may request permissions separately for its own tool calls. To avoid repeated prompts, use `PreToolUse` hooks to auto-approve specific tools, or configure permission rules, which subagents [inherit from the parent conversation](../09-Agents-Patterns/sub-agents.md#permission-modes).


[​](#recursive-hook-loops-with-subagents)

Recursive hook loops with subagents

A `UserPromptSubmit` hook that spawns subagents can create infinite loops if those subagents trigger the same hook. To prevent this:

- Use a shared variable or session state to track whether you’re already inside a subagent
- Scope hooks to only run for the top-level agent session


[​](#systemmessage-not-appearing-in-output)

systemMessage not appearing in output

The `systemMessage` field shows a message to the user, not the model. On Claude Code v2.1.227 or later, a hook’s `systemMessage` can surface in the message stream as an [`SDKInformationalMessage`](agent-sdk-typescript.md#sdkinformationalmessage). Whether it does depends on the event. Each [event’s section](../07-Hooks/hooks.md#hook-events) on the hooks page says how output surfaces. To pass context to the model instead, return [`additionalContext`](../07-Hooks/hooks.md#add-context-for-claude). Before v2.1.227, the SDK surfaced hook output in the message stream only for `SessionStart` and `Setup` hooks. For any other event, the output appeared only in the lifecycle events that [`includeHookEvents`](agent-sdk-typescript.md#options) (`include_hook_events` in Python) adds. That option’s entry covers which lifecycle events each hook event produces. If you need to surface hook decisions to your application reliably, log them separately or use a dedicated output channel.


[​](#related-resources)

Related resources

- [Claude Code hooks reference](../07-Hooks/hooks.md): full JSON input/output schemas, event documentation, and matcher patterns
- [Claude Code hooks guide](../07-Hooks/hooks-guide.md): shell command hook examples and walkthroughs
- [TypeScript SDK reference](agent-sdk-typescript.md): hook types, input/output definitions, and configuration options
- [Python SDK reference](agent-sdk-python.md): hook types, input/output definitions, and configuration options
- [Permissions](agent-sdk-permissions.md): control what your agent can do
- [Custom tools](agent-sdk-custom-tools.md): build tools to extend agent capabilities

---

## Hosting the Agent SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/hosting
Path: 05-Agent-SDK/agent-sdk-hosting.md

## On this page

- [The subprocess model](#the-subprocess-model)
  - [State that lives on local disk](#state-that-lives-on-local-disk)
- [Choose a session pattern](#choose-a-session-pattern)
  - [Ephemeral sessions](#ephemeral-sessions)
  - [Long-running sessions](#long-running-sessions)
  - [Hybrid sessions](#hybrid-sessions)
  - [Multi-agent container](#multi-agent-container)
- [Provision the container](#provision-the-container)
  - [Container-based sandboxing](#container-based-sandboxing)
  - [Runtime dependencies](#runtime-dependencies)
  - [Resources](#resources)
  - [Network](#network)
- [Handle production concerns](#handle-production-concerns)
  - [Session and state persistence](#session-and-state-persistence)
  - [Observability](#observability)
  - [Auth and secrets](#auth-and-secrets)
  - [Scaling and concurrency](#scaling-and-concurrency)
  - [Cost](#cost)
  - [Multi-tenant isolation](#multi-tenant-isolation)
- [Known limitations](#known-limitations)
- [Troubleshoot deployment failures](#troubleshoot-deployment-failures)
- [Next steps](#next-steps)

Deployment

# Hosting the Agent SDK

Copy pageCopy page

Deploy the Agent SDK in production: subprocess architecture, session persistence, scaling, observability, and multi-tenant isolation for Docker, Kubernetes, and sandbox providers.

Copy pageCopy page

The Agent SDK spawns and supervises a `claude` CLI subprocess that owns a shell, a working directory, and session files on disk. Hosting it is not like hosting a stateless API wrapper. Every running agent is a long-lived process tied to local state, which shapes how you allocate resources, persist sessions, and scale across tenants. This page covers self-hosting on your own infrastructure. For deployable Dockerfiles and Kubernetes manifests, see the [hosting cookbook](https://github.com/anthropics/claude-cookbooks/tree/main/claude_agent_sdk/hosting). If you don’t need to run the agent loop itself on your own infrastructure, consider [Managed Agents](https://code.claude.com/docs/en/04-API-Reference/Other/managed-agents-overview-b12cd03916.md) instead. Anthropic hosts the agent loop, and your application sends events and receives streamed results through the client SDKs or the REST API. Tool execution runs in an Anthropic-managed cloud sandbox or a [self-hosted sandbox](../04-API-Reference/Other/managed-agents-self-hosted-sandboxes.md) on your own infrastructure.


[​](#the-subprocess-model)

The subprocess model

Every hosting decision on this page follows from how the SDK runs the agent. When your code calls `query()`, the SDK spawns a separate `claude` CLI process and talks to it over stdio. That subprocess owns the shell, the working directory, and the JSONL session transcripts on local disk.

One agent session maps to one subprocess. Running N concurrent sessions means N subprocesses, each with its own process tree and transcript file. By default they all inherit your application’s working directory. When sessions need separate filesystems, pass a distinct `cwd` in the options of each session’s `query()` call:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Summarize the files in this directory",
  options: { cwd: "/work/session-a" },
})) {
  console.log(message);
}
```

```python
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, query


async def main():
    async for message in query(
        prompt="Summarize the files in this directory",
        options=ClaudeAgentOptions(cwd="/work/session-a"),
    ):
        print(message)


asyncio.run(main())
```

The TypeScript examples on this page use top-level `await`, so save them as `.mts` files or set `"type": "module"` in `package.json`.


[​](#state-that-lives-on-local-disk)

State that lives on local disk

Three kinds of agent state live on the container’s filesystem by default. None of them survive a container restart, a scale-down, or a move to a different node.

| State                       | Default location                                                                                 |
|-----------------------------|--------------------------------------------------------------------------------------------------|
| Session transcripts         | `~/.claude/projects/`, or the `projects/` directory under `CLAUDE_CONFIG_DIR` if set             |
| `CLAUDE.md` memory files    | `~/.claude/CLAUDE.md` for the user tier and the session’s working directory for the project tier |
| Working-directory artifacts | The session’s working directory                                                                  |

To persist transcripts across hosts, configure a [`SessionStore` adapter](agent-sdk-session-storage.md). Memory files and other working-directory artifacts need their own storage strategy, such as a mounted volume or an object-store sync. For how sessions, resumption, and forking work at the API level, see [Sessions](agent-sdk-sessions.md).


[​](#choose-a-session-pattern)

Choose a session pattern

These four patterns cover session lifecycle: how long a container lives relative to the sessions it serves. For where the container runs, the [hosting cookbook](https://github.com/anthropics/claude-cookbooks/blob/main/claude_agent_sdk/07_Hosting_the_agent.ipynb) has [deployable code](https://github.com/anthropics/claude-cookbooks/tree/main/claude_agent_sdk/hosting) for local Docker, Modal, and Kubernetes. Choose a session pattern here and a deployment target from the cookbook.


[​](#ephemeral-sessions)

Ephemeral sessions

Create a container for each user task and destroy it when the task completes. Best for one-off tasks. The user may still interact with the AI while the task is completing, but once completed the container is destroyed. Example workloads include bug investigation and fix, invoice and receipt extraction, document translation, and media transformation. The container runs a one-shot entrypoint that reads the task from the `TASK_PROMPT` environment variable, calls the SDK, and exits.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const prompt = process.env.TASK_PROMPT!;
for await (const message of query({ prompt, options: { maxTurns: 20 } })) {
  console.log(message);
}
```

```python
import asyncio
import os

from claude_agent_sdk import ClaudeAgentOptions, query


async def main():
    async for message in query(
        prompt=os.environ["TASK_PROMPT"],
        options=ClaudeAgentOptions(max_turns=20),
    ):
        print(message)


asyncio.run(main())
```

The script prints each message as it arrives, including a result message whose `subtype` is `success` when the task completes within the turn limit. If the task hits the 20-turn limit instead, the result message’s `subtype` is `error_max_turns` and the `query()` call raises an error after yielding it, so wrap the loop in a try block if the container needs to exit cleanly. See [Handle the result](agent-sdk-agent-loop.md#handle-the-result) for the error subtypes.


[​](#long-running-sessions)

Long-running sessions

Run persistent container instances, often hosting multiple SDK processes per container, to serve ongoing work. Best for agents that take autonomous action, serve content, or handle high-volume message streams. Example workloads include an email agent that triages and responds to incoming mail, a site builder that hosts a per-user editable site through container ports, and a chat bot that handles continuous traffic from a platform like Slack. The container exposes an HTTP or WebSocket endpoint and maps each active session to a long-lived query and the subprocess behind it. The calls that keep sessions open and warm differ between the SDKs:

- **TypeScript**: use [`streamInput()`](agent-sdk-typescript.md#query-object) to add turns to an active session. Call [`startup()`](agent-sdk-typescript.md#startup) to pre-warm subprocesses ahead of incoming traffic. If you don’t know a session’s working directory until its first request arrives, pre-warm with [`prewarm()`](agent-sdk-typescript.md#prewarm) instead.
- **Python**: use [`ClaudeSDKClient`](agent-sdk-python.md#claudesdkclient) to hold a session open across turns.

Size the container so it can hold the maximum number of concurrent sessions in memory.


[​](#hybrid-sessions)

Hybrid sessions

Ephemeral containers that hydrate from a [`SessionStore`](agent-sdk-session-storage.md) on startup and persist updates back. Best for sessions that span many interactions but sit idle between them. The container spins down during idle periods and spins back up when the user returns. Example workloads include a personal project manager with intermittent check-ins, deep research that pauses and resumes over hours, and a customer support agent that loads ticket history across interactions. Tune your provider’s idle timeout to how frequently you expect users to return. Shutting a container down without a `SessionStore` configured loses the transcript with it, so the store is required for this pattern, not optional. The pattern hinges on resuming a session by ID with a shared store attached:

TypeScript

Python

```python
import { query, type SessionStore } from "@anthropic-ai/claude-agent-sdk";

declare const userInput: string;
declare const sessionId: string;          // looked up from your database by user
declare const sessionStore: SessionStore; // an object store, key-value store, database, or your own adapter

for await (const message of query({
  prompt: userInput,
  options: { resume: sessionId, sessionStore },
})) {
  // ...
}
```

```python
from claude_agent_sdk import query, ClaudeAgentOptions, SessionStore
import asyncio

user_input: str = ...
session_id: str = ...              # looked up from your database by user
session_store: SessionStore = ...  # an object store, key-value store, database, or your own adapter


async def main():
    async for message in query(
        prompt=user_input,
        options=ClaudeAgentOptions(
            resume=session_id,
            session_store=session_store,
        ),
    ):
        ...


asyncio.run(main())
```


[​](#multi-agent-container)

Multi-agent container

Run multiple SDK subprocesses inside one container. Best for agents that must collaborate closely, for example multi-agent simulations where the agents interact with each other in a shared environment. Give each agent its own working directory so they do not overwrite each other’s files, and isolate settings loading so per-agent `CLAUDE.md` files do not leak across agents. See [Multi-tenant isolation](#multi-tenant-isolation) for the specific options.


[​](#provision-the-container)

Provision the container


[​](#container-based-sandboxing)

Container-based sandboxing

Run the SDK inside a sandboxed container for process isolation, resource limits, network control, and an ephemeral filesystem. Questions to answer when choosing a provider:

- **Who runs the sandbox**: a sandbox-as-a-service provider operates the infrastructure for you, while self-hosted options give you software to run on your own.
- **Cold-start latency**: how long from “create a sandbox” to “ready to accept the first request.” Ephemeral patterns need sub-second starts. Long-running patterns tolerate more.
- **Persistent storage**: whether the provider offers durable volumes or only ephemeral disk. The hybrid pattern needs durable storage somewhere, whether in the sandbox or alongside it.
- **Pricing model**: per-second, per-request, or flat hourly billing. Per-second pricing suits bursty ephemeral workloads. Hourly suits long-running sessions.
- **Networking**: support for custom egress rules, outbound proxies, and private VPC peering for regulated environments.

For self-hosted options such as Docker, gVisor, and Firecracker, and detailed isolation configuration, see [Isolation Technologies](agent-sdk-secure-deployment.md#isolation-technologies).


[​](#runtime-dependencies)

Runtime dependencies

The container needs your SDK’s language runtime:

- Python 3.10+ for the Python SDK, or Node.js 18+ for the TypeScript SDK
- Both the TypeScript and Python SDKs bundle a native Claude Code binary for most installs, and the spawned CLI needs no separate Node.js install. See the [quickstart’s install note](agent-sdk-quickstart.md) for the installs that need a separate native Claude Code install.

The bundled binary is pinned to the SDK package version, so updating the SDK is how you update the CLI. The SDK follows semver: take patch releases continuously and review the [TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md) or [Python](https://github.com/anthropics/claude-agent-sdk-python/blob/main/CHANGELOG.md) changelog before taking a minor.


[​](#resources)

Resources

1 GiB RAM, 5 GiB disk, and 1 CPU per agent is a reasonable starting point for a freshly started instance. Memory usage grows with session length and tool activity, so size for the session lengths and concurrency you actually need rather than the idle baseline. See [Scaling and concurrency](#scaling-and-concurrency) for how to work out agents per host.


[​](#network)

Network

The SDK needs outbound HTTPS to `api.anthropic.com`, or to your provider’s regional endpoint when running on Amazon Bedrock or Google Cloud’s Agent Platform. If your agents use [MCP servers](agent-sdk-mcp.md) or external tools, they need outbound access to those endpoints as well. For production, route outbound traffic through an egress proxy that enforces domain allowlists, injects credentials, and logs requests. See [Secure Deployment](agent-sdk-secure-deployment.md) for the full pattern. For inbound traffic, expose an HTTP or WebSocket port on the container. Your application handles client requests on that port and calls the SDK internally; the subprocess itself does not listen on the network.


[​](#handle-production-concerns)

Handle production concerns

Work through these decisions before shipping a self-hosted agent.


[​](#session-and-state-persistence)

Session and state persistence

Default local disk is lost on restart, scale-down, or a move to a different node. For any session a user expects to resume, mirror the transcript to durable storage with a [`SessionStore` adapter](agent-sdk-session-storage.md). See [Reference implementations](agent-sdk-session-storage.md#reference-implementations) for example adapters for an object store, a key-value store, and a database, and a conformance suite for your own. Three things to know about how `SessionStore` behaves:

- **Transcripts only**: `SessionStore` mirrors transcripts, not `CLAUDE.md` memory files or other working-directory artifacts. Mount a shared volume or sync those separately.
- **Mirror, not replacement**: the subprocess writes to local disk first, and the SDK forwards a copy of each batch to the store. A fresh session’s local transcript outlives the run; a run resumed from the store deletes its local copy at the end, so the store holds the only durable copy. See [Dual-write architecture](agent-sdk-session-storage.md#dual-write-architecture).
- **`mirror_error` messages**: when the SDK can’t deliver a batch to the store, it drops the batch, emits a `{ type: "system", subtype: "mirror_error" }` message, and continues the query. Alert on these if store durability matters. See [Mirror writes are best-effort](agent-sdk-session-storage.md#mirror-writes-are-best-effort) for the retry and timeout behavior.


[​](#observability)

Observability

Agent SDK agents are long-lived processes that spawn tool calls across many API round-trips. Without telemetry you cannot see which tools ran, how long they took, or where a session stalled. The SDK inherits OpenTelemetry configuration from the environment. Set the OTEL environment variables at the container or orchestrator level so every `query()` call exports spans, metrics, and log events to your collector. The example below enables OTLP export for all three signals. `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA` is required only for traces; omit it if you export metrics and logs alone.

.env

```python
CLAUDE_CODE_ENABLE_TELEMETRY=1
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector.example.com:4318
```

Prompt text and tool inputs are not included in exports by default. See [Control sensitive data in exports](agent-sdk-observability.md#control-sensitive-data-in-exports) for the opt-in flags, and [Observability](agent-sdk-observability.md) for the full signal catalog.


[​](#auth-and-secrets)

Auth and secrets

Three auth concerns matter at hosting time:

- **Anthropic API**: the subprocess reads `ANTHROPIC_API_KEY` from its environment. Supply it from your secret manager, or set `ANTHROPIC_BASE_URL` to route model calls through a proxy that injects the key outside the container. See [Credential management](agent-sdk-secure-deployment.md#credential-management) for the proxy pattern and [Setup in the SDK quickstart](agent-sdk-quickstart.md#setup) for supported authentication methods.
- **Inbound**: put authentication at a gateway in front of the agent container. The agent should receive pre-authenticated requests and should not be the component that validates user tokens.
- **Outbound tools**: keep tool credentials out of the agent environment. Route outbound calls through a proxy that injects API keys after the request leaves the container. The agent makes the call; the proxy adds the credential.


[​](#scaling-and-concurrency)

Scaling and concurrency

Each session runs in its own subprocess, so concurrency on a host is bounded by how many subprocesses its RAM can hold. Size each host with this formula:

```python
agents per host = (host RAM - overhead) / (per-session RAM ceiling)
```

Measure the per-session ceiling by running a representative session to your target length under your expected tool load and recording peak RSS. The 1 GiB starting point in [Resources](#resources) is a floor, not the ceiling. Horizontal-scale routing depends on your pattern. For long-running sessions, where containers hold many sessions, run a pool of containers behind a load balancer and pin each session to one container using consistent hashing on `sessionId`. A pinned session keeps hitting the same container, and therefore the same running subprocess, until it is evicted or the container restarts.


[​](#cost)

Cost

Anthropic token cost typically dominates container infrastructure cost by an order of magnitude or more. A minimally provisioned container runs roughly \$0.05 per hour, while a single long agent session can spend dollars in tokens. See [Cost tracking](agent-sdk-cost-tracking.md) for per-session token accounting.


[​](#multi-tenant-isolation)

Multi-tenant isolation

Default SDK behavior reads settings and `CLAUDE.md` memory files from the filesystem. In a shared container that serves multiple tenants, those files can leak one tenant’s context into another tenant’s session. To isolate tenants inside a shared container:

- Pass `settingSources: []` in TypeScript or `setting_sources=[]` in Python to skip user, project, and local settings.
- Set `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` in `env`. [Auto memory](../02-Claude-Code-CLI/memory.md#auto-memory) at `~/.claude/projects/<project>/memory/` loads into the system prompt regardless of `settingSources`. See [What settingSources does not control](agent-sdk-claude-code-features.md#what-settingsources-does-not-control) for the other inputs that load unconditionally.
- Point `CLAUDE_CONFIG_DIR` at a per-tenant directory so tenants do not share the `~/.claude.json` global config. When each config directory serves one working directory and you don’t pass a [`SessionStore`](agent-sdk-session-storage.md), you can also set [`CLAUDE_CODE_PROJECT_DIR_NAME`](../02-Claude-Code-CLI/sessions.md#name-the-project-directory-yourself) in `env` to keep the transcript paths under it short. Requires TypeScript Agent SDK v0.3.234 or later, or Python Agent SDK v0.2.140 or later.
- Use a per-tenant working directory. Pass `cwd` explicitly on every `query()` call.
- Apply per-tenant egress rules at your proxy, such as distinct outbound IPs, credentials, or domain allowlists, so a compromised tenant cannot exfiltrate data via another tenant’s outbound policy.

The example below applies the settings, auto memory, config directory, and working directory options together. Construct `tenantDir` and `configDir` so each tenant gets a path no other tenant can read. In TypeScript, `env` replaces the subprocess environment, so spread `...process.env` to keep inherited variables like `PATH` and `ANTHROPIC_API_KEY`. In Python, `env` is merged on top of the inherited environment.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

declare const prompt: string;
declare const tenantDir: string;
declare const configDir: string;

for await (const message of query({
  prompt,
  options: {
    cwd: tenantDir,
    settingSources: [],
    env: {
      ...process.env,
      CLAUDE_CONFIG_DIR: configDir,
      CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
    },
  },
})) {
  // ...
}
```

```python
from claude_agent_sdk import query, ClaudeAgentOptions
import asyncio

prompt: str = ...
tenant_dir: str = ...
config_dir: str = ...


async def main():
    async for message in query(
        prompt=prompt,
        options=ClaudeAgentOptions(
            cwd=tenant_dir,
            setting_sources=[],
            env={
                "CLAUDE_CONFIG_DIR": config_dir,
                "CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1",
            },
        ),
    ):
        ...


asyncio.run(main())
```


[​](#known-limitations)

Known limitations

Plan around these in your deployment design.

| Limitation                                          | What to do                                                                                                                                                                                                                                    |
|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| No top-level session timeout                        | A session does not time out on its own. Set `maxTurns` in TypeScript or `max_turns` in Python to bound how many tool-use round trips the agent takes before stopping.                                                                         |
| Memory growth over long sessions                    | Cap session length or recycle subprocesses periodically. See [Scaling and concurrency](#scaling-and-concurrency).                                                                                                                             |
| Large parallel-subagent fanouts can hit rate limits | Break work into smaller batches rather than issuing one wide dispatch.                                                                                                                                                                        |
| No per-subagent wall-clock deadline                 | Cap each [subagent](agent-sdk-subagents.md) with `maxTurns` in its `AgentDefinition`. `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS` sets a stall watchdog that fires when a subagent stops producing output; it isn’t a total-runtime deadline. |


[​](#troubleshoot-deployment-failures)

Troubleshoot deployment failures

Use this section when an agent that works on your machine fails in a deployed service. Each item below names a failure and links the entry that covers it:

- **CLI not found at service start**: in Python, a container or service manager runs your application with a different `PATH` than your shell, so an install that works locally isn’t visible to the process. In TypeScript, the image build skipped the SDK’s optional dependencies, or `pathToClaudeCodeExecutable` points at a file that doesn’t exist in the image. See [Claude Code not found](agent-sdk-troubleshooting.md#clinotfounderror-claude-code-not-found).
- **CLI present in the image but won’t launch**: Claude Code can’t start from a binary that doesn’t match the container’s architecture or libc, or from a file that lost its execute permission in the image build. See [Failed to start Claude Code](agent-sdk-troubleshooting.md#cliconnectionerror-failed-to-start-claude-code).
- **Claude Code process exits mid-run**: the error your application receives depends on the SDK language and on whether the CLI reported an error result first. The entries under [CLI process exit](agent-sdk-troubleshooting.md#cli-process-exit) cover each message.


[​](#next-steps)

Next steps

- [Hosting cookbook](https://github.com/anthropics/claude-cookbooks/blob/main/claude_agent_sdk/07_Hosting_the_agent.ipynb): notebook walkthrough with [deployable code](https://github.com/anthropics/claude-cookbooks/tree/main/claude_agent_sdk/hosting) for Docker, Modal, and Kubernetes.
- [Session storage](agent-sdk-session-storage.md): persist transcripts across hosts with a `SessionStore` adapter.
- [Observability](agent-sdk-observability.md): export OTEL traces, metrics, and logs to your collector.
- [Secure deployment](agent-sdk-secure-deployment.md): network controls, credential management, and isolation hardening.
- [Cost tracking](agent-sdk-cost-tracking.md): per-session token and cost accounting.

---

## Connect to external tools with MCP - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/mcp
Path: 05-Agent-SDK/agent-sdk-mcp.md

## On this page

- [Quickstart](#quickstart)
- [Add an MCP server](#add-an-mcp-server)
  - [Add a server in code](#add-a-server-in-code)
  - [Add a server from a config file](#add-a-server-from-a-config-file)
- [Connection timing](#connection-timing)
- [Allow MCP tools](#allow-mcp-tools)
  - [Tool naming convention](#tool-naming-convention)
  - [Auto-approve with allowedTools](#auto-approve-with-allowedtools)
  - [Discover available tools](#discover-available-tools)
- [Transport types](#transport-types)
  - [stdio servers](#stdio-servers)
  - [HTTP/SSE servers](#http%2Fsse-servers)
  - [SDK MCP servers](#sdk-mcp-servers)
- [MCP tool search](#mcp-tool-search)
- [Authentication](#authentication)
  - [Pass credentials via environment variables](#pass-credentials-via-environment-variables)
  - [HTTP headers for remote servers](#http-headers-for-remote-servers)
  - [OAuth2 authentication](#oauth2-authentication)
- [Examples](#examples)
  - [List issues from a repository](#list-issues-from-a-repository)
  - [Query a database](#query-a-database)
- [Error handling](#error-handling)
- [Troubleshooting](#troubleshooting)
  - [Server shows “failed” status](#server-shows-%E2%80%9Cfailed%E2%80%9D-status)
  - [Tools not being called](#tools-not-being-called)
  - [A tool is missing from an SDK MCP server](#a-tool-is-missing-from-an-sdk-mcp-server)
  - [Connection timeouts](#connection-timeouts)
  - [Tool output exceeds maximum allowed tokens](#tool-output-exceeds-maximum-allowed-tokens)
- [Related resources](#related-resources)

Extend with tools

# Connect to external tools with MCP

Copy pageCopy page

Configure MCP servers to extend your agent with external tools. Covers transport types, tool search for large tool sets, authentication, and error handling.

Copy pageCopy page

The [Model Context Protocol (MCP)](../06-MCP-Tools/General/getting-started-intro.md) is an open standard for connecting AI agents to external tools and data sources. With MCP, your agent can query databases, integrate with APIs like Slack and GitHub, and connect to other services without writing custom tool implementations. MCP servers can run as local processes, connect over HTTP, or execute directly within your SDK application.

This page covers MCP configuration for the Agent SDK. To add MCP servers to the Claude Code CLI so they load in every project, see [MCP installation scopes](../06-MCP-Tools/General/mcp.md#mcp-installation-scopes).


[​](#quickstart)

Quickstart

This example connects to the [Claude Code documentation](../02-Claude-Code-CLI/code-home.md) MCP server using [HTTP transport](#http%2Fsse-servers) and uses [`allowedTools`](#allow-mcp-tools) with a wildcard to permit all tools from the server.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Use the docs MCP server to explain what hooks are in Claude Code",
  options: {
    mcpServers: {
      "claude-code-docs": {
        type: "http",
        url: "https://code.claude.com/docs/mcp"
      }
    },
    allowedTools: ["mcp__claude-code-docs__*"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            "claude-code-docs": {
                "type": "http",
                "url": "https://code.claude.com/docs/mcp",
            }
        },
        allowed_tools=["mcp__claude-code-docs__*"],
    )

    async for message in query(
        prompt="Use the docs MCP server to explain what hooks are in Claude Code",
        options=options,
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

The agent connects to the documentation server, searches for information about hooks, and returns the results.


[​](#add-an-mcp-server)

Add an MCP server

You can configure MCP servers in code when calling `query()`, or in a `.mcp.json` file loaded via [`settingSources`](#from-a-config-file).


[​](#add-a-server-in-code)

Add a server in code

Pass MCP servers directly in the `mcpServers` option. This example starts a local filesystem MCP server for `/Users/me/projects`. Replace that path with a directory on your machine:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List files in my project",
  options: {
    mcpServers: {
      filesystem: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
      }
    },
    allowedTools: ["mcp__filesystem__*"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            "filesystem": {
                "command": "npx",
                "args": [
                    "-y",
                    "@modelcontextprotocol/server-filesystem",
                    "/Users/me/projects",
                ],
            }
        },
        allowed_tools=["mcp__filesystem__*"],
    )

    async for message in query(prompt="List files in my project", options=options):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```


[​](#add-a-server-from-a-config-file)

Add a server from a config file

Create a `.mcp.json` file at your project root. The file is picked up when the `project` setting source is enabled, which it is for default `query()` options. If you set `settingSources` explicitly, include `"project"` for this file to load. Replace `/Users/me/projects` with a directory on your machine:

```python
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
    }
  }
}
```


[​](#connection-timing)

Connection timing

Claude Code registers the servers you pass in `options.mcpServers` at startup and emits the [init message](#error-handling) once the first-turn wait, if any, resolves. Whether each `options.mcpServers` server delays the first turn, and when it connects, depends on its type:

| Server type                                                                            | Delays the first turn?                                 | First-turn wait timeout                                                                                               |
|:---------------------------------------------------------------------------------------|:-------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| stdio server, or HTTP/SSE server without a cached tool list                            | Yes, until it connects                                 | [`MCP_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md), 30 seconds by default; the connection fails at that deadline                      |
| Remote server with a cached tool list, saved by Claude Code from a previous connection | No; the cached tools are available from the first turn | None; connects on its first tool call, and that deferred connect has its own timeout                                  |
| In-process [SDK server](#sdk-mcp-servers)                                              | Yes, until it connects and lists its tools             | [`MCP_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md), 30 seconds by default, per connect attempt; the connection fails at that deadline |

Servers loaded from [settings files](#from-a-config-file) such as `.mcp.json` or from plugins commonly show `pending` in the init message. When `options.mcpServers` holds a stdio, HTTP, or SSE server, the first turn waits for these pending servers too, up to `MCP_TIMEOUT`. When `options.mcpServers` is empty or holds only SDK servers, the first turn waits up to 2 seconds instead:

- **With [tool search](agent-sdk-tool-search.md), the default**: the wait covers still-pending servers configured with [`alwaysLoad: true`](../06-MCP-Tools/General/mcp.md#exempt-a-server-from-deferral) and not the rest. The rest keep connecting in the background. [Tool availability](../06-MCP-Tools/General/mcp.md#tool-availability) describes how Claude reaches their tools once they connect.
- **Without tool search**: the wait covers every pending server. [Configure tool search](agent-sdk-tool-search.md#configure-tool-search) covers what turns tool search off. If you exclude the `ToolSearch` tool from the session, for example through `disallowedTools`, the session also runs without tool search.

If you set `permissionPromptToolName`, the first turn also waits for that tool’s server in every case, up to `MCP_TIMEOUT`. To set the first-turn wait yourself, add `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` to the [`env` option](agent-sdk-configuration.md#set-environment-variables), for example `CLAUDE_CODE_MCP_STARTUP_WAIT_MS: "5000"`. The first turn then waits up to that many milliseconds for every pending server, whether or not tool search is available. This deadline also replaces the `MCP_TIMEOUT` first-turn wait for stdio, HTTP, and SSE servers in `options.mcpServers`. `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` requires Claude Code v2.1.274 or later. Servers still pending when the wait ends keep connecting in the background. Set the variable to `0` to skip the wait. A `permissionPromptToolName` server keeps its own `MCP_TIMEOUT` wait regardless of the value. To block startup itself at a separate, earlier phase than the first-turn wait, before the init message is sent:

- Set [`MCP_CONNECTION_NONBLOCKING`](../02-Claude-Code-CLI/env-vars.md) to `0` to block on the whole connection batch. Claude Code caps that wait at 5 seconds by default. Adjust the cap with the [`MCP_CONNECT_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md) environment variable, in milliseconds. Servers still pending at that deadline keep connecting in the background.
- Set `alwaysLoad: true` on a server’s config to make its tools available at their full schemas on the first turn, [exempt from tool search deferral](../06-MCP-Tools/General/mcp.md#exempt-a-server-from-deferral). Claude Code waits at startup for that server’s tools, capped at the same deadline, while other servers keep connecting in the background; a remote server with a cached tool list supplies them without connecting, per the table above.

The `system` message with subtype `init` reports each server’s status at the moment it’s emitted; see [Error handling](#error-handling) for reading those statuses.


[​](#allow-mcp-tools)

Allow MCP tools

MCP tools require explicit permission before Claude can use them. Without permission, Claude will see that tools are available but won’t be able to call them.


[​](#tool-naming-convention)

Tool naming convention

MCP tools follow the naming pattern `mcp__<server-name>__<tool-name>`. For example, a GitHub server named `"github"` with a `list_issues` tool becomes `mcp__github__list_issues`.


[​](#auto-approve-with-allowedtools)

Auto-approve with allowedTools

Use `allowedTools` to auto-approve specific MCP tools so Claude can use them without a permission prompt:

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      // your servers
    },
    allowedTools: [
      "mcp__github__*", // All tools from the github server
      "mcp__db__query", // Only the query tool from db server
      "mcp__slack__send_message" // Only send_message from slack server
    ]
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        # your servers
    },
    allowed_tools=[
        "mcp__github__*",  # All tools from the github server
        "mcp__db__query",  # Only the query tool from db server
        "mcp__slack__send_message",  # Only send_message from slack server
    ],
)
```

Wildcards (`*`) let you allow all tools from a server without listing each one individually.

**Prefer `allowedTools` over permission modes for MCP access.** `permissionMode: "acceptEdits"` does not auto-approve MCP tools (only file edits and filesystem Bash commands). `permissionMode: "bypassPermissions"` does auto-approve MCP tools but also disables most other safety prompts, which is broader than necessary; see [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) for the prompts that remain. A wildcard in `allowedTools` grants exactly the MCP server you want and nothing more. See [Permission modes](agent-sdk-permissions.md#permission-modes) for a full comparison.


[​](#discover-available-tools)

Discover available tools

To see what tools an MCP server provides, check the server’s documentation or inspect the `tools` array in the `system` init message. MCP tool names start with `mcp__`. Claude Code emits the init message after the [first-turn connection wait](#connection-timing) for servers passed in `options.mcpServers`, so the `tools` array lists the `mcp__` tools of each server that has connected by then, plus those of servers with a [cached tool list](#connection-timing), which connect on first use. Tools of any other server that hasn’t connected are absent; see [Error handling](#error-handling) for reading each server’s status. This filter prints the MCP tool names:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const options = {
  mcpServers: {
    // your servers
  },
};

for await (const message of query({ prompt: "...", options })) {
  if (message.type === "system" && message.subtype === "init") {
    const mcpTools = message.tools.filter((name) => name.startsWith("mcp__"));
    console.log("Available MCP tools:", mcpTools);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            # your servers
        },
    )
    async for message in query(prompt="...", options=options):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            mcp_tools = [t for t in message.data.get("tools", []) if t.startswith("mcp__")]
            print("Available MCP tools:", mcp_tools)


asyncio.run(main())
```

You can also ask Claude to list the tools available from a server.


[​](#transport-types)

Transport types

MCP servers communicate with your agent using different transport protocols. Check the server’s documentation to see which transport it supports:

- If the docs give you a **command to run** (like `npx @modelcontextprotocol/server-filesystem`), use stdio
- If the docs give you a **URL**, use HTTP or SSE
- If you’re building your own tools in code, use an SDK MCP server


[​](#stdio-servers)

stdio servers

Local processes that communicate via stdin/stdout. Use this for MCP servers you run on the same machine. For the `.mcp.json` form, use the same fields shown at [Add a server from a config file](#from-a-config-file). In code, pass the command and its arguments. Replace `/Users/me/projects` with a directory on your machine:

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      filesystem: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
      }
    },
    allowedTools: ["mcp__filesystem__read_file", "mcp__filesystem__list_directory"]
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        "filesystem": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/Users/me/projects",
            ],
        }
    },
    allowed_tools=["mcp__filesystem__read_file", "mcp__filesystem__list_directory"],
)
```


[​](#http/sse-servers)

HTTP/SSE servers

Use HTTP or SSE for cloud-hosted MCP servers and remote APIs. For the `.mcp.json` form, use the same fields as the example at [HTTP headers for remote servers](#http-headers-for-remote-servers), with `"type": "sse"` for an SSE server. In code, pass the server’s URL:

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      "remote-api": {
        type: "sse",
        url: "https://api.example.com/mcp/sse",
        headers: {
          Authorization: `Bearer ${process.env.API_TOKEN}`
        }
      }
    },
    allowedTools: ["mcp__remote-api__*"]
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        "remote-api": {
            "type": "sse",
            "url": "https://api.example.com/mcp/sse",
            "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
        }
    },
    allowed_tools=["mcp__remote-api__*"],
)
```

For the streamable HTTP transport, use `"type": "http"` instead. In `.mcp.json` and other JSON config files, `"streamable-http"` is accepted as an alias for `"http"`. The SDKs’ `McpHttpServerConfig` type declares only `"http"`, so use `"http"` for servers you pass in code.


[​](#sdk-mcp-servers)

SDK MCP servers

Define custom tools directly in your application code instead of running a separate server process. See the [custom tools guide](agent-sdk-custom-tools.md) for implementation details. An SDK MCP server registered by an [`initialize` control request](agent-sdk-typescript.md#sdkcontrolinitializeresponse) begins connecting as soon as Claude Code processes the request.


[​](#mcp-tool-search)

MCP tool search

When you have many MCP tools configured, tool definitions can consume a significant portion of your context window. Tool search solves this by withholding tool definitions from context and loading only the ones Claude needs for each turn. Tool search is enabled by default. See [Tool search](agent-sdk-tool-search.md) for configuration options, best practices, and using tool search with custom SDK tools.


[​](#authentication)

Authentication

Most MCP servers require authentication to access external services. Pass credentials through environment variables in the server configuration.


[​](#pass-credentials-via-environment-variables)

Pass credentials via environment variables

Use the `env` field to pass API keys, tokens, and other credentials to the MCP server:

- In code

- .mcp.json

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      "api-server": {
        command: "npx",
        args: ["-y", "@your-org/api-mcp-server"],
        env: {
          API_KEY: process.env.API_KEY
        }
      }
    },
    allowedTools: ["mcp__api-server__*"]
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        "api-server": {
            "command": "npx",
            "args": ["-y", "@your-org/api-mcp-server"],
            "env": {"API_KEY": os.environ["API_KEY"]},
        }
    },
    allowed_tools=["mcp__api-server__*"],
)
```

```python
{
  "mcpServers": {
    "api-server": {
      "command": "npx",
      "args": ["-y", "@your-org/api-mcp-server"],
      "env": {
        "API_KEY": "${API_KEY}"
      }
    }
  }
}
```

The `${API_KEY}` syntax expands environment variables at runtime.


[​](#http-headers-for-remote-servers)

HTTP headers for remote servers

For HTTP and SSE servers, pass authentication headers directly in the server configuration:

- In code

- .mcp.json

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      "secure-api": {
        type: "http",
        url: "https://api.example.com/mcp",
        headers: {
          Authorization: `Bearer ${process.env.API_TOKEN}`
        }
      }
    },
    allowedTools: ["mcp__secure-api__*"]
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        "secure-api": {
            "type": "http",
            "url": "https://api.example.com/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
        }
    },
    allowed_tools=["mcp__secure-api__*"],
)
```

```python
{
  "mcpServers": {
    "secure-api": {
      "type": "http",
      "url": "https://api.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${API_TOKEN}"
      }
    }
  }
}
```

The `${API_TOKEN}` syntax expands environment variables at runtime.

For a complete working example of a remote server authenticated with headers, see [List issues from a repository](#list-issues-from-a-repository).


[​](#oauth2-authentication)

OAuth2 authentication

The [MCP specification supports OAuth 2.1](../06-MCP-Tools/Spec-Archive/2025-03-26-basic-authorization.md) for authorization. The SDK doesn’t open a browser or run an interactive OAuth flow. When a configured server returns an authorization challenge and no stored token is available, the agent run continues without that server’s tools, and the server reports status `needs-auth`. The `mcp_servers` array of the [system init message](agent-sdk-typescript.md#sdksystemmessage) may still show `pending` for that server when it’s emitted. To confirm whether a server needs credentials, poll `mcpServerStatus()` in the TypeScript SDK or [`get_mcp_status()`](agent-sdk-python.md#methods) in Python. To supply credentials, complete the OAuth flow in your own application and pass the resulting access token in the server’s `headers`:

TypeScript

Python

```python
// After completing OAuth flow in your app.
// Implement getAccessTokenFromOAuthFlow for your OAuth provider.
const accessToken = await getAccessTokenFromOAuthFlow();

const options = {
  mcpServers: {
    "oauth-api": {
      type: "http",
      url: "https://api.example.com/mcp",
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }
  },
  allowedTools: ["mcp__oauth-api__*"]
};
```

```python
# After completing OAuth flow in your app.
# Implement get_access_token_from_oauth_flow for your OAuth provider.
access_token = await get_access_token_from_oauth_flow()

options = ClaudeAgentOptions(
    mcp_servers={
        "oauth-api": {
            "type": "http",
            "url": "https://api.example.com/mcp",
            "headers": {"Authorization": f"Bearer {access_token}"},
        }
    },
    allowed_tools=["mcp__oauth-api__*"],
)
```


[​](#examples)

Examples


[​](#list-issues-from-a-repository)

List issues from a repository

This example connects to the remote [GitHub MCP server](https://github.com/github/github-mcp-server) to list recent issues. The example includes debug logging to verify the MCP connection and tool calls. Before running, create a [GitHub personal access token](https://github.com/settings/personal-access-tokens) with read access to the repositories you want to query and set it as an environment variable:

```python
export GITHUB_TOKEN=YOUR_GITHUB_PAT
```

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List the 3 most recent issues in anthropics/claude-code",
  options: {
    mcpServers: {
      github: {
        type: "http",
        url: "https://api.githubcopilot.com/mcp/",
        headers: {
          Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
        }
      }
    },
    allowedTools: ["mcp__github__list_issues"]
  }
})) {
  // Verify MCP server connected successfully
  if (message.type === "system" && message.subtype === "init") {
    console.log("MCP servers:", message.mcp_servers);
  }

  // Log when Claude calls an MCP tool
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use" && block.name.startsWith("mcp__")) {
        console.log("MCP tool called:", block.name);
      }
    }
  }

  // Print the final result
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
import os
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    ResultMessage,
    SystemMessage,
    AssistantMessage,
)


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            "github": {
                "type": "http",
                "url": "https://api.githubcopilot.com/mcp/",
                "headers": {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
            }
        },
        allowed_tools=["mcp__github__list_issues"],
    )

    async for message in query(
        prompt="List the 3 most recent issues in anthropics/claude-code",
        options=options,
    ):
        # Verify MCP server connected successfully
        if isinstance(message, SystemMessage) and message.subtype == "init":
            print("MCP servers:", message.data.get("mcp_servers"))

        # Log when Claude calls an MCP tool
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "name") and block.name.startswith("mcp__"):
                    print("MCP tool called:", block.name)

        # Print the final result
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

In the `MCP servers:` line, a `status` of `connected` for `github` confirms the token works. If Claude Code has a [cached tool list](#connection-timing) for the server, the status can read `pending` instead and the server connects on its first tool call. If the status is `failed` or `needs-auth`, see [Error handling](#error-handling) before trusting the result, since Claude can fall back to built-in tools when the server is unavailable.


[​](#query-a-database)

Query a database

This example uses [DBHub](https://github.com/bytebase/dbhub) to query a Postgres database. The agent automatically discovers the database schema, writes the SQL query, and returns the results. DBHub’s `execute_sql` tool runs whatever SQL the agent emits, including writes, unless you restrict it. Setting `readonly = true` in the [DBHub configuration file](https://dbhub.ai/config/toml) makes DBHub reject `INSERT`, `UPDATE`, `DELETE`, and DDL statements, so the example cannot modify your data even if the agent emits a write. DBHub resolves `${DATABASE_URL}` from the process environment when it loads the config, so the connection string stays out of the file. Create this `dbhub.toml` next to your script:

dbhub.toml

```python
[[sources]]
id = "production"
dsn = "${DATABASE_URL}"

[[tools]]
name = "execute_sql"
source = "production"
readonly = true
```

The script then points DBHub at the config file instead of passing a connection string directly. Before running, set the `DATABASE_URL` environment variable to your connection string. Replace the placeholder values with your own database details:

```python
export DATABASE_URL=postgresql://user:password@localhost:5432/mydb
```

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  // Natural language query - Claude writes the SQL
  prompt: "How many users signed up last week? Break it down by day.",
  options: {
    mcpServers: {
      postgres: {
        command: "npx",
        // dbhub.toml sets readonly = true, so execute_sql rejects writes
        args: ["-y", "@bytebase/dbhub", "--config", "dbhub.toml"]
      }
    },
    allowedTools: ["mcp__postgres__execute_sql"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            "postgres": {
                "command": "npx",
                # dbhub.toml sets readonly = true, so execute_sql rejects writes
                "args": [
                    "-y",
                    "@bytebase/dbhub",
                    "--config",
                    "dbhub.toml",
                ],
            }
        },
        allowed_tools=["mcp__postgres__execute_sql"],
    )

    # Natural language query - Claude writes the SQL
    async for message in query(
        prompt="How many users signed up last week? Break it down by day.",
        options=options,
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```


[​](#error-handling)

Error handling

MCP servers can fail to connect for various reasons: the server process might not be installed, credentials might be invalid, or a remote server might be unreachable. Claude Code emits a `system` message with subtype `init` at the start of each query. This message includes the connection status for each MCP server. The `status` field can be `"pending"`, `"connected"`, `"failed"`, `"needs-auth"`, or `"disabled"`. Claude Code emits the init message after the [first-turn connection wait](#connection-timing) for servers passed in `options.mcpServers`, so such a server that connected within the wait shows `"connected"`. In the init message, don’t treat `"pending"` as a failure on its own. It can mean any of these:

- The server hasn’t connected yet. See [how long Claude Code waits for it before the first turn](#connection-timing)
- The server’s tool list was [served from the cache](#connection-timing), with a connection made on first use
- The connection deadline expired. Such a server reports `"pending"` or `"failed"` depending on timing

Check for `"failed"` or `"needs-auth"` to detect servers that won’t be usable:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({
    prompt: "Process data",
    options: {
      mcpServers: {
        // Replace dataServer with your server configuration
        "data-processor": dataServer
      }
    }
  })) {
    if (message.type === "system" && message.subtype === "init") {
      const unavailableServers = message.mcp_servers.filter(
        (s) => s.status === "failed" || s.status === "needs-auth"
      );

      if (unavailableServers.length > 0) {
        console.warn("Unavailable MCP servers:", unavailableServers);
      }
    }

    if (message.type === "result" && message.subtype === "error_during_execution") {
      console.error("Execution failed");
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branch above has
  // already run; a failure to start or reach the Claude Code process
  // yields no result message. MCP servers that fail to connect don't
  // throw: use the status check above, and note that servers still
  // "pending" at init need a later status check.
  console.log(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, ResultMessage


async def main():
    # Replace data_server with your server configuration
    options = ClaudeAgentOptions(mcp_servers={"data-processor": data_server})

    try:
        async for message in query(prompt="Process data", options=options):
            if isinstance(message, SystemMessage) and message.subtype == "init":
                unavailable_servers = [
                    s
                    for s in message.data.get("mcp_servers", [])
                    if s.get("status") in ("failed", "needs-auth")
                ]

                if unavailable_servers:
                    print(f"Unavailable MCP servers: {unavailable_servers}")

            if (
                isinstance(message, ResultMessage)
                and message.subtype == "error_during_execution"
            ):
                print("Execution failed")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branch above has
        # already run; a failure to start or reach the Claude Code process
        # yields no result message. MCP servers that fail to connect don't
        # raise: use the status check above, and note that servers still
        # "pending" at init need a later status check.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

A remote server’s status can also change after it reports `"connected"`. When the connection to it drops mid-session, Claude Code moves the server back to `"pending"` while [reconnecting](../06-MCP-Tools/General/mcp.md#automatic-reconnection). A later `mcpServerStatus()` call in TypeScript, or [`ClaudeSDKClient.get_mcp_status()`](agent-sdk-python.md#methods) in Python, can then report `"pending"` for a server you saw connected earlier, with no configuration change on your side. After five reconnection attempts fail, the server reports `"failed"`, or `"needs-auth"` when it needs authorizing again. To retry manually, call [`reconnectMcpServer()`](agent-sdk-typescript.md#methods) in TypeScript or [`ClaudeSDKClient.reconnect_mcp_server()`](agent-sdk-python.md#methods) in Python.


[​](#troubleshooting)

Troubleshooting


[​](#server-shows-“failed”-status)

Server shows “failed” status

Check the `init` message to see which servers failed to connect:

TypeScript

Python

```python
if (message.type === "system" && message.subtype === "init") {
  for (const server of message.mcp_servers) {
    if (server.status === "failed") {
      console.error(`Server ${server.name} failed to connect`);
    }
  }
}
```

```python
if isinstance(message, SystemMessage) and message.subtype == "init":
    for server in message.data.get("mcp_servers", []):
        if server.get("status") == "failed":
            print(f"Server {server['name']} failed to connect")
```

A `"pending"` status doesn’t mean the server failed. See [Error handling](#error-handling) for the cases it covers at init. To get updated statuses later in the session, call the query’s `mcpServerStatus()` method in the TypeScript SDK, or [`ClaudeSDKClient.get_mcp_status()`](agent-sdk-python.md#methods) in Python. Common causes:

- **Missing environment variables**: Ensure required tokens and credentials are set. For stdio servers, check the `env` field matches what the server expects.
- **Server not installed**: For `npx` commands, verify the package exists and Node.js is in your PATH.
- **Invalid connection string**: For database servers, verify the connection string format and that the database is accessible.
- **Network issues**: For remote HTTP/SSE servers, check the URL is reachable and any firewalls allow the connection.


[​](#tools-not-being-called)

Tools not being called

If Claude sees tools but doesn’t use them, check that you’ve granted permission with `allowedTools`:

TypeScript

Python

```python
const _ = {
  options: {
    mcpServers: {
      // your servers
    },
    allowedTools: ["mcp__servername__*"] // Auto-approve calls from this server
  }
};
```

```python
options = ClaudeAgentOptions(
    mcp_servers={
        # your servers
    },
    allowed_tools=["mcp__servername__*"],  # Auto-approve calls from this server
)
```


[​](#a-tool-is-missing-from-an-sdk-mcp-server)

A tool is missing from an SDK MCP server

In the TypeScript SDK, when a tool’s input schema can’t be converted to JSON Schema, the server you created with [`createSdkMcpServer()`](agent-sdk-typescript.md#createsdkmcpserver) leaves that tool out when it lists its tools. The SDK emits a warning at that point. Under Node.js the warning is a process warning with code `CLAUDE_SDK_MCP_TOOL_SCHEMA_UNCONVERTIBLE`, and it starts with this text:

```python
Tool "<name>" on SDK MCP server "<server>" was left out of the server's tool list, because its input schema cannot be converted to JSON Schema
```

The rest of the warning gives the conversion error’s message when it has one, then says what to check and change. Before TypeScript Agent SDK v0.3.286, one unconvertible schema made the server’s whole tool listing fail without this warning, so none of that server’s tools reached Claude.


[​](#connection-timeouts)

Connection timeouts

MCP server connections time out after 30 seconds by default. To change how long a running tool call may take, set [`MCP_TOOL_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md). If your server takes longer to start, the connection fails. Raise the connection limit with the [`MCP_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md) environment variable, in milliseconds. For servers that need more startup time, also consider:

- Using a lighter-weight server if available
- Pre-warming the server before starting your agent
- Checking server logs for slow initialization causes

In TypeScript, you can set the tool-call limit for a single [SDK MCP server](#sdk-mcp-servers) by passing [`timeout` to `createSdkMcpServer()`](agent-sdk-typescript.md#createsdkmcpserver).


[​](#tool-output-exceeds-maximum-allowed-tokens)

Tool output exceeds maximum allowed tokens

The SDK applies the same MCP output limits as Claude Code. When a successful tool result with no image content is larger than 25,000 tokens, Claude Code saves the output to a file and replaces the tool result with an error message that names the file path, so the agent can read the output back in portions. To change the token limit, set the [`MAX_MCP_OUTPUT_TOKENS`](../02-Claude-Code-CLI/env-vars.md) environment variable. Unless the tool declares `anthropic/maxResultSizeChars`, a successful text result longer than 50,000 characters is saved to a file regardless of the token limit. See [MCP output limits and warnings](../06-MCP-Tools/General/mcp.md#mcp-output-limits-and-warnings) for the full behavior, including how a server declares that annotation.


[​](#related-resources)

Related resources

- **[Custom tools guide](agent-sdk-custom-tools.md)**: Build your own MCP server that runs in-process with your SDK application
- **[Permissions](agent-sdk-permissions.md)**: Control which MCP tools your agent can use with `allowedTools` and `disallowedTools`
- **[TypeScript SDK reference](agent-sdk-typescript.md)**: Full API reference including MCP configuration options
- **[Python SDK reference](agent-sdk-python.md)**: Full API reference including MCP configuration options
- **[MCP server directory](https://github.com/modelcontextprotocol/servers)**: Browse available MCP servers for databases, APIs, and more

---

## Migrate to Claude Agent SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/migration-guide
Path: 05-Agent-SDK/agent-sdk-migration-guide.md

## On this page

- [Overview](#overview)
- [What’s Changed](#what%E2%80%99s-changed)
- [Migration Steps](#migration-steps)
  - [Migrate a TypeScript or JavaScript project](#migrate-a-typescript-or-javascript-project)
  - [Migrate a Python project](#migrate-a-python-project)
- [Breaking changes](#breaking-changes)
  - [Python: ClaudeCodeOptions renamed to ClaudeAgentOptions](#python-claudecodeoptions-renamed-to-claudeagentoptions)
  - [System prompt no longer default](#system-prompt-no-longer-default)
  - [Settings sources default](#settings-sources-default)
- [Next Steps](#next-steps)

Agent SDK

# Migrate to Claude Agent SDK

Copy pageCopy page

Guide for migrating the Claude Code TypeScript and Python SDKs to the Claude Agent SDK

Copy pageCopy page


[​](#overview)

Overview

The Claude Code SDK has been renamed to the **Claude Agent SDK** and its documentation has been reorganized. This change reflects the SDK’s broader capabilities for building AI agents beyond coding tasks. Migrating from the OpenAI Agents SDK instead? The [OpenAI Agents SDK migration recipe](https://platform.claude.com/cookbook/claude-agent-sdk-04-migrating-from-openai-agents-sdk) maps each primitive onto the Claude Agent SDK through a single worked example.


[​](#what’s-changed)

What’s Changed

| Aspect                     | Old                         | New                                                                           |
|:---------------------------|:----------------------------|:------------------------------------------------------------------------------|
| **Package Name (TS/JS)**   | `@anthropic-ai/claude-code` | `@anthropic-ai/claude-agent-sdk`                                              |
| **Python Package**         | `claude-code-sdk`           | `claude-agent-sdk`                                                            |
| **Documentation Location** | Claude Code docs            | Claude Code docs → dedicated [Agent SDK](agent-sdk-overview.md) section |


[​](#migration-steps)

Migration Steps


[​](#migrate-a-typescript-or-javascript-project)

Migrate a TypeScript or JavaScript project

**1. Uninstall the old package:**

```python
npm uninstall @anthropic-ai/claude-code
```

**2. Install the new package:**

```python
npm install @anthropic-ai/claude-agent-sdk
```

**3. Update your imports:** Change all imports from `@anthropic-ai/claude-code` to `@anthropic-ai/claude-agent-sdk`:

```python
// Before
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-code";

// After
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
```

**4. Update package.json:** If `@anthropic-ai/claude-code` is still listed in your `package.json`, replace it with `@anthropic-ai/claude-agent-sdk` and update the version range as well, for example from `"^0.0.42"` to `"^0.3.0"`. **5. Review [breaking changes](#breaking-changes)** Make any code changes needed to complete the migration.


[​](#migrate-a-python-project)

Migrate a Python project

**1. Uninstall the old package:**

```python
pip uninstall -y claude-code-sdk
```

If the old package isn’t installed, pip prints `WARNING: Skipping claude-code-sdk as it is not installed.` That’s expected and you can continue to the next step. **2. Install the new package:**

```python
pip install claude-agent-sdk
```

If `claude-code-sdk` is listed in your `requirements.txt` or `pyproject.toml`, replace it with `claude-agent-sdk`. **3. Update your imports:** Change all imports from `claude_code_sdk` to `claude_agent_sdk`:

```python
# Before
from claude_code_sdk import query, ClaudeCodeOptions

# After
from claude_agent_sdk import query, ClaudeAgentOptions
```

**4. Review [breaking changes](#breaking-changes)** Make any code changes needed to complete the migration.


[​](#breaking-changes)

Breaking changes

To improve isolation and explicit configuration, Claude Agent SDK v0.1.0 introduces breaking changes for users migrating from Claude Code SDK.


[​](#python-claudecodeoptions-renamed-to-claudeagentoptions)

Python: ClaudeCodeOptions renamed to ClaudeAgentOptions

**What changed:** The Python SDK type `ClaudeCodeOptions` has been renamed to `ClaudeAgentOptions`. **Migration:**

```python
# BEFORE (claude-code-sdk)
from claude_code_sdk import query, ClaudeCodeOptions

options = ClaudeCodeOptions(model="claude-opus-4-7", permission_mode="acceptEdits")

# AFTER (claude-agent-sdk)
from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(model="claude-opus-4-7", permission_mode="acceptEdits")
```


[​](#system-prompt-no-longer-default)

System prompt no longer default

**What changed:** The SDK no longer uses Claude Code’s system prompt by default. **Migration:**

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// BEFORE (v0.0.x) - Used Claude Code's system prompt by default
const before = query({ prompt: "Hello" });

// AFTER (v0.1.0) - Uses minimal system prompt by default
// To get the old behavior, explicitly request Claude Code's preset:
const presetResult = query({
  prompt: "Hello",
  options: {
    systemPrompt: { type: "preset", preset: "claude_code" }
  }
});

// Or use a custom system prompt:
const customResult = query({
  prompt: "Hello",
  options: {
    systemPrompt: "You are a helpful coding assistant"
  }
});
```

```python
from claude_agent_sdk import query, ClaudeAgentOptions
import asyncio


async def main():
    # BEFORE (v0.0.x) - Used Claude Code's system prompt by default
    async for message in query(prompt="Hello"):
        print(message)

    # AFTER (v0.1.0) - Uses minimal system prompt by default
    # To get the old behavior, explicitly request Claude Code's preset:
    async for message in query(
        prompt="Hello",
        options=ClaudeAgentOptions(
            system_prompt={"type": "preset", "preset": "claude_code"}  # Use the preset
        ),
    ):
        print(message)

    # Or use a custom system prompt:
    async for message in query(
        prompt="Hello",
        options=ClaudeAgentOptions(system_prompt="You are a helpful coding assistant"),
    ):
        print(message)


asyncio.run(main())
```


[​](#settings-sources-default)

Settings sources default

This default was briefly changed in v0.1.0 to load no filesystem settings and then reverted, so no migration action is needed. **Current behavior:** Omitting `settingSources` on `query()` loads user, project, and local filesystem settings, matching the CLI. This includes `~/.claude/settings.json`, `.claude/settings.json`, `.claude/settings.local.json`, CLAUDE.md files, and custom commands. To run isolated from filesystem settings, pass `settingSources: []`, or `setting_sources=[]` in Python. See [Control filesystem settings with settingSources](agent-sdk-claude-code-features.md#control-filesystem-settings-with-settingsources) for what each source loads. Isolation is especially important for CI/CD pipelines, deployed applications, test environments, and multi-tenant systems where local customizations should not leak in.

Python SDK 0.1.59 and earlier treated an empty list the same as omitting the option, so upgrade before relying on `setting_sources=[]`. See [What settingSources does not control](agent-sdk-claude-code-features.md#what-settingsources-does-not-control) for inputs that are read even when `settingSources` is `[]`.


[​](#next-steps)

Next Steps

- Explore the [Agent SDK Overview](agent-sdk-overview.md) to learn about available features
- Check out the [TypeScript SDK Reference](agent-sdk-typescript.md) for detailed API documentation
- Review the [Python SDK Reference](agent-sdk-python.md) for Python-specific documentation
- Learn about [Custom Tools](agent-sdk-custom-tools.md) and [MCP Integration](agent-sdk-mcp.md)

---

## Modifying system prompts - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts
Path: 05-Agent-SDK/agent-sdk-modifying-system-prompts.md

## On this page

- [How system prompts work](#how-system-prompts-work)
  - [Decide on a starting point](#decide-on-a-starting-point)
- [Customize agent behavior](#customize-agent-behavior)
  - [CLAUDE.md files for project-level instructions](#claude-md-files-for-project-level-instructions)
  - [Load CLAUDE.md with the SDK](#load-claude-md-with-the-sdk)
  - [Output styles for persistent configurations](#output-styles-for-persistent-configurations)
  - [Create an output style](#create-an-output-style)
  - [Activate an output style](#activate-an-output-style)
  - [Append to the claude_code preset](#append-to-the-claude_code-preset)
  - [Improve prompt caching across users and machines](#improve-prompt-caching-across-users-and-machines)
  - [Custom system prompts](#custom-system-prompts)
  - [Cache the static part of a custom prompt](#cache-the-static-part-of-a-custom-prompt)
  - [Change the prompt of an existing session](#change-the-prompt-of-an-existing-session)
  - [Update Claude’s instructions mid-session](#update-claude%E2%80%99s-instructions-mid-session)
  - [Turn recording off while you iterate on wording](#turn-recording-off-while-you-iterate-on-wording)
- [Context Claude Code adds outside the system prompt](#context-claude-code-adds-outside-the-system-prompt)
  - [Reminders Claude Code adds to the conversation](#reminders-claude-code-adds-to-the-conversation)
  - [Turn off the context your agent replaces](#turn-off-the-context-your-agent-replaces)
  - [See what Claude received](#see-what-claude-received)
- [Compare the four approaches](#compare-the-four-approaches)
- [Combine approaches](#combine-approaches)
  - [Combine an output style with session-specific additions](#combine-an-output-style-with-session-specific-additions)
- [See also](#see-also)

Customize behavior

# Modifying system prompts

Copy pageCopy page

Choose between the `claude_code` preset and a custom system prompt, and customize behavior with CLAUDE.md, output styles, append, or a fully custom prompt.

Copy pageCopy page

System prompts define Claude’s behavior, capabilities, and response style. Start from the `claude_code` preset for CLI or IDE-like coding tools where a human watches and steers the work. Write your own prompt for agents with a different surface, identity, or permission model.


[​](#how-system-prompts-work)

How system prompts work

A system prompt is the initial instruction set that shapes how Claude behaves throughout a conversation. The Agent SDK has three starting points for it:

- **Minimal default**: when you don’t set `systemPrompt` in TypeScript or `system_prompt` in Python, the SDK uses a minimal prompt that covers tool calling but omits the rest of the `claude_code` preset’s content, including its security and safety instructions. This differs from `claude -p`, which uses the Claude Code system prompt by default. If you’re migrating from the CLI and want matching behavior, set the `claude_code` preset.
- **`claude_code` preset**: the system prompt that the Claude Code CLI uses, with tool usage instructions and security and safety instructions. Set `systemPrompt: { type: "preset", preset: "claude_code" }` in TypeScript or `system_prompt={"type": "preset", "preset": "claude_code"}` in Python, optionally with `append` to add your own instructions on the end.
- **Custom string**: a prompt you write yourself. The SDK sends only what you provide.


[​](#decide-on-a-starting-point)

Decide on a starting point

The deciding factor is how closely your agent resembles Claude Code: a coding agent operating in a repository, with a human watching streaming output and steering the work. The further your product is from that, the more you’ll want to write your own prompt.

| You’re building                                                                                              | Use                                | What you get                                                                                                                  |
|:-------------------------------------------------------------------------------------------------------------|:-----------------------------------|:------------------------------------------------------------------------------------------------------------------------------|
| A CLI or IDE-like coding tool where a human watches and steers, and Claude Code’s defaults are what you want | `claude_code` preset               | The Claude Code prompt, including tool guidance and safety rules                                                              |
| The same kind of tool, plus product-specific rules like coding standards, output format, or domain context   | `claude_code` preset with `append` | Everything above, with your instructions added after the preset. Nothing is removed, so this is the lowest-risk customization |
| An agent with a different surface, identity, or permission model, or a non-coding agent                      | Custom prompt string               | Only what you write. You take responsibility for replacing the tool guidance and safety instructions your agent still needs   |
| A thin tool-calling loop with no agent persona, where you supply all behavior in the user prompt             | No `systemPrompt` option           | The minimal default: tool-calling support and nothing else                                                                    |

“Different from Claude Code” usually means one of the following:

- **Different surface**: the output isn’t read in a terminal by the person who triggered it. Chat UIs, structured-output consumers, and non-coding automation each need a prompt that matches how their output is rendered and reviewed. Unattended coding automation, like a CI job that fixes lint errors or reviews diffs, still fits the preset because the work itself is what the preset is written for.
- **Different identity**: the agent shouldn’t present itself as Claude Code. A support bot, a data-analysis assistant, or any domain-specific agent needs its own name, scope, and persona.
- **Different permission model**: the agent runs autonomously without a human approving each step, or operates on a narrow set of resources. Claude Code’s prompt assumes a human is in the loop with access to a full toolset.
- **Non-coding tasks**: most of Claude Code’s prompt is coding guidance. For research, content, or operations agents, that guidance competes with the instructions you actually need.

The [comparison table](#compare-the-four-approaches) shows what each customization method preserves.


[​](#customize-agent-behavior)

Customize agent behavior

`append` and a custom prompt string each change the system prompt directly, and an output style changes the instructions Claude Code gives Claude for every response. CLAUDE.md takes a different path: the SDK reads it and injects its content into the conversation as project context, so it shapes behavior alongside whichever system prompt you choose. [Skills](agent-sdk-skills.md), [hooks](agent-sdk-hooks.md), and [permissions](agent-sdk-permissions.md) also shape behavior outside the system prompt and are covered on their own pages.


[​](#claude-md-files-for-project-level-instructions)

CLAUDE.md files for project-level instructions

CLAUDE.md files give Claude persistent project context and instructions. The SDK injects their content into the conversation and leaves the system prompt untouched, so they work with any system prompt configuration. For what to put in CLAUDE.md, where to place it, and how to write effective instructions, see [When to add to CLAUDE.md](../02-Claude-Code-CLI/memory.md#when-to-add-to-claude-md) and the rest of [How Claude remembers your project](../02-Claude-Code-CLI/memory.md). This section covers what’s specific to the SDK: how CLAUDE.md loads. The SDK reads CLAUDE.md when the matching setting source is enabled: `'project'` loads `CLAUDE.md` or `.claude/CLAUDE.md` from the working directory, and `'user'` loads `~/.claude/CLAUDE.md`. Default `query()` options enable both sources, so CLAUDE.md loads automatically. If you set `settingSources` in TypeScript or `setting_sources` in Python explicitly, include the sources you need. CLAUDE.md loading is controlled by setting sources, not by the `claude_code` preset.


[​](#load-claude-md-with-the-sdk)

Load CLAUDE.md with the SDK

To load CLAUDE.md, set `settingSources` to include the level where you keep your CLAUDE.md. The example below loads a project-level CLAUDE.md alongside the `claude_code` preset, so Claude has both the coding-agent prompt and your project’s conventions:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const messages = [];

for await (const message of query({
  prompt: "Add a new React component for user profiles",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code" // Use Claude Code's system prompt
    },
    settingSources: ["project"] // Loads CLAUDE.md from project
  }
})) {
  messages.push(message);
}

// Now Claude has access to your project guidelines from CLAUDE.md
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

messages = []


async def main():
    async for message in query(
        prompt="Add a new React component for user profiles",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",  # Use Claude Code's system prompt
            },
            setting_sources=["project"],  # Loads CLAUDE.md from project
        ),
    ):
        messages.append(message)


asyncio.run(main())

# Now Claude has access to your project guidelines from CLAUDE.md
```

When you run either example, the SDK streams messages as Claude works: a system init message, assistant messages, user messages carrying tool results, and a final result message with the session outcome. CLAUDE.md is persistent across all sessions in a project, shared with your team through git, and discovered automatically without code changes. It is not loaded if you pass an empty `settingSources` array.


[​](#output-styles-for-persistent-configurations)

Output styles for persistent configurations

Output styles are saved sets of instructions that change Claude’s role, tone, and output format. They’re stored as markdown files and can be reused across sessions and projects.


[​](#create-an-output-style)

Create an output style

An output style is a markdown file with [frontmatter](../02-Claude-Code-CLI/output-styles.md#frontmatter) for metadata, followed by the prompt content. Save it to `~/.claude/output-styles/` for a user-level style available in every project, or `.claude/output-styles/` in your repository for a project-level style you can commit and share with your team. A custom output style leaves the `claude_code` preset’s software engineering instructions out and uses your own. To keep them and layer your instructions on top, set `keep-coding-instructions: true` in the frontmatter. Those instructions are only in Claude Code’s full system prompt, so the setting has no effect in a session on the shorter system prompt; set [`CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT`](../02-Claude-Code-CLI/env-vars.md#variables) to `0` to select the full prompt on any model. Keep them when your agent is still doing software engineering work. Leave them out when you’re replacing the role entirely. The example below defines a code-review persona that keeps the coding instructions, since reviewing code still benefits from Claude Code’s security and code-quality guidance. Save it as `~/.claude/output-styles/code-reviewer.md` to make it available across projects:

~/.claude/output-styles/code-reviewer.md

```python
---
name: Code Reviewer
description: Thorough code review assistant
keep-coding-instructions: true
---

You are an expert code reviewer.

For every code submission:
1. Check for bugs and security issues
2. Evaluate performance
3. Suggest improvements
4. Rate code quality (1-10)
```


[​](#activate-an-output-style)

Activate an output style

Once created, activate output styles via:

- **CLI**: run `/output-style <style>`, for example `/output-style concise`, or run `/config` and select one. The `/output-style` command requires Claude Code v2.1.269 or later.
- **Settings**: set `outputStyle` in `.claude/settings.local.json`
- **TypeScript SDK**: set `outputStyle` inside the inline `settings` object passed to `query()`, or point `settings` at a settings file that sets it. `outputStyle` is not a top-level `Options` field:

  ``` shiki
  const options = { settings: { outputStyle: "Explanatory" } };
  ```

In the Python SDK, set `outputStyle` through the `settings` option, which takes a JSON string such as `'{"outputStyle": "Explanatory"}'` or a path to a settings file that sets it. **Note for SDK users:** Output styles are loaded when you include `settingSources: ['user']` or `settingSources: ['project']` (TypeScript) / `setting_sources=["user"]` or `setting_sources=["project"]` (Python) in your options.


[​](#append-to-the-claude_code-preset)

Append to the `claude_code` preset

You can use the Claude Code preset with an `append` property to add your custom instructions while preserving all built-in functionality.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const messages = [];

for await (const message of query({
  prompt: "Help me write a Python function to calculate fibonacci numbers",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code",
      append: "Always include detailed docstrings and type hints in Python code."
    }
  }
})) {
  messages.push(message);
  if (message.type === "assistant") {
    console.log(message.message.content);
  }
}
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage

messages = []


async def main():
    async for message in query(
        prompt="Help me write a Python function to calculate fibonacci numbers",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": "Always include detailed docstrings and type hints in Python code.",
            }
        ),
    ):
        messages.append(message)
        if isinstance(message, AssistantMessage):
            print(message.content)


asyncio.run(main())
```


[​](#improve-prompt-caching-across-users-and-machines)

Improve prompt caching across users and machines

By default, two sessions that use the same `claude_code` preset and `append` text still can’t share a prompt cache entry when their auto memory locations differ. The preset embeds that location in the system prompt ahead of your `append` text. The location defaults to an absolute path under `~/.claude/projects/` named for the repository’s path on disk, so it differs across users, machines, and checkouts. CLAUDE.md content and environment details such as the working directory, platform, shell, and OS version don’t affect the system prompt cache, because Claude Code delivers them in the conversation, not the system prompt. To make the system prompt identical across sessions, set `excludeDynamicSections: true` in TypeScript or `"exclude_dynamic_sections": True` in Python. The per-user context moves into the first user message, leaving only the static preset and your `append` text in the system prompt so identical configurations share a cache entry across users and machines.

`excludeDynamicSections` requires `@anthropic-ai/claude-agent-sdk` v0.2.98 or later, or `claude-agent-sdk` v0.1.58 or later for Python. Set it on the preset object form only. The SDK ignores it when you pass a custom prompt instead of the preset; to keep a custom prompt’s instructions cached in the TypeScript SDK, see [Cache the static part of a custom prompt](#cache-the-static-part-of-a-custom-prompt).

The following example pairs a shared `append` block with `excludeDynamicSections` so a fleet of agents can reuse the same cached system prompt:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Triage the open issues in this repo",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code",
      append: "You operate Acme's internal triage workflow. Label issues by component and severity.",
      excludeDynamicSections: true
    }
  }
})) {
  // ...
}
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Triage the open issues in this repo",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": "You operate Acme's internal triage workflow. Label issues by component and severity.",
                "exclude_dynamic_sections": True,
            },
        ),
    ):
        ...


asyncio.run(main())
```

**Tradeoffs:** the text that moves out of the system prompt still reaches Claude, but in a user message. That text is at least the auto memory directory’s location, and often the whole auto memory section. Instructions in a user message carry marginally less weight than the same text in the system prompt, so Claude may follow its auto memory guidance less consistently. Enable this option when cross-session cache reuse matters more than that. For the equivalent flag in non-interactive CLI mode, see [`--exclude-dynamic-system-prompt-sections`](../02-Claude-Code-CLI/cli-reference.md).


[​](#custom-system-prompts)

Custom system prompts

You can provide a custom string as `systemPrompt` to replace the default entirely with your own instructions.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const customPrompt = `You are a Python coding specialist.
Follow these guidelines:
- Write clean, well-documented code
- Use type hints for all functions
- Include comprehensive docstrings
- Prefer functional programming patterns when appropriate
- Always explain your code choices`;

const messages = [];

for await (const message of query({
  prompt: "Create a data processing pipeline",
  options: {
    systemPrompt: customPrompt
  }
})) {
  messages.push(message);
  if (message.type === "assistant") {
    console.log(message.message.content);
  }
}
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage

custom_prompt = """You are a Python coding specialist.
Follow these guidelines:
- Write clean, well-documented code
- Use type hints for all functions
- Include comprehensive docstrings
- Prefer functional programming patterns when appropriate
- Always explain your code choices"""

messages = []


async def main():
    async for message in query(
        prompt="Create a data processing pipeline",
        options=ClaudeAgentOptions(system_prompt=custom_prompt),
    ):
        messages.append(message)
        if isinstance(message, AssistantMessage):
            print(message.content)


asyncio.run(main())
```

In Python, load a large custom prompt from a file with `system_prompt={"type": "file", "path": "..."}` instead of passing it as a string. The Python SDK passes a string prompt as one command-line argument to the CLI subprocess, so a prompt that exceeds the OS argument-length limit fails at process spawn before any API request is sent. On Linux the error is `Argument list too long`. See [`SystemPromptFile`](agent-sdk-python.md#systempromptfile) for the platform thresholds and the Windows behavior.


[​](#cache-the-static-part-of-a-custom-prompt)

Cache the static part of a custom prompt

In the TypeScript SDK, you can pass a custom prompt as an array of strings instead of one string, with the `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` marker between the static part and the rest. Use this when your prompt combines instructions that are the same on every request with context that changes per request, such as the customer or ticket the agent is handling. When you pass both parts as one string, a change to the per-request part changes the whole system prompt, so the static instructions miss the cache too. The array form isn’t available in the Python SDK; [`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) lists the forms `system_prompt` accepts.

Claude Code splits the prompt only when it calls the Claude API directly or runs on [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md). In every other configuration, such as Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or an [LLM gateway](../13-Enterprise-Admin/llm-gateway-connect.md), it sends the whole prompt as one block, the same as passing one string. The same happens whenever you set [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](../13-Enterprise-Admin/llm-gateway-protocol.md#disable-pre-release-capabilities).

To split the prompt, import `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` from `@anthropic-ai/claude-agent-sdk` and pass it as its own array element between the two parts. The SDK sends the strings before the marker as one text block and the strings after it as a second block, each with its own cache breakpoint. In the example below, a support agent loads its triage instructions from a file and receives details about one ticket on each request, so the instructions stay cached while the ticket details change:

TypeScript

```python
import { readFile } from "node:fs/promises";
import { query, SYSTEM_PROMPT_DYNAMIC_BOUNDARY } from "@anthropic-ai/claude-agent-sdk";

// Identical on every request
const instructions = await readFile("triage-instructions.md", "utf8");
// Different on every request
const ticketContext = "Customer plan: Enterprise. Other open tickets from this customer: 3.";

for await (const message of query({
  prompt: "Triage ticket 4821",
  options: {
    systemPrompt: [instructions, SYSTEM_PROMPT_DYNAMIC_BOUNDARY, ticketContext]
  }
})) {
  // ...
}
```

[Track cache tokens](agent-sdk-cost-tracking.md#track-cache-tokens) describes the `cache_creation_input_tokens` and `cache_read_input_tokens` fields on each result message. The SDK assembles the blocks from the array as follows:

- The SDK joins the strings on each side of the marker with a blank line between them and removes the marker itself, so the marker text doesn’t reach Claude.
- If you include the marker more than once, the first one is the split and the SDK removes the others.
- If you leave the marker out, the SDK joins all the strings into one block, the same as passing one string.

With the CLI’s [`--system-prompt` or `--system-prompt-file` flags](../02-Claude-Code-CLI/cli-reference.md#system-prompt-flags), the prompt is one string, so there is no array to carry the marker. Include a line containing only `__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__` between the static and per-request parts instead. Claude Code splits the prompt at the first such line into the same two blocks and removes that line. Requires Claude Code v2.1.275 or later. In the SDK, prefer the array form, which carries the boundary without a marker line.


[​](#change-the-prompt-of-an-existing-session)

Change the prompt of an existing session

By default, if you pass a different `append` or custom prompt when you return to a session with `resume` or `continue`, Claude doesn’t see it on the next turn. Claude Code records the system prompt on a session’s first request and reuses that record until the session is compacted. The new text takes effect after that compaction, or in a new session.


[​](#update-claude’s-instructions-mid-session)

Update Claude’s instructions mid-session

If the instructions you put in the system prompt need to change while a session is running, for example because your user switched the agent to a read-only mode or edited its configuration in your app, send the new instructions in the conversation instead of changing `systemPrompt`:

- **In your next message**: include the new instructions in the next user message you send.
- **From a hook**: return [`additionalContext`](../07-Hooks/hooks.md#add-context-for-claude) from a `UserPromptSubmit` or `PostToolUse` [hook callback](agent-sdk-hooks.md#outputs), written as a factual statement such as “The workspace is now read-only”. The SDK inserts the text into the conversation at the point where the hook fired, so the recorded prompt stays unchanged.


[​](#turn-recording-off-while-you-iterate-on-wording)

Turn recording off while you iterate on wording

While you iterate on prompt wording and want each edit to reach a session you resume, set `snapshot` to false on the object form of the system prompt. Claude Code then rebuilds the prompt on every request. The field is available on the preset and custom forms of [`systemPrompt`](agent-sdk-typescript.md#options) in TypeScript and of [`system_prompt`](agent-sdk-python.md#systempromptpreset) in Python, and requires `@anthropic-ai/claude-agent-sdk` v0.3.257 or later, or `claude-agent-sdk` v0.2.153 or later. Keep recording on in production. With recording off, a different `append` or custom prompt on a resumed session reaches Claude on the next turn, and that request can’t reuse the session’s [prompt cache](../02-Claude-Code-CLI/prompt-caching.md#how-the-cache-is-organized). Where the API enforces [preserved thinking](../04-API-Reference/Guides/build-with-claude-preserved-thinking.md), Claude also loses its thinking from earlier turns. Outside of [cloud sessions](../02-Claude-Code-CLI/cloud-environments.md), if you start Claude Code in [bare mode](../02-Claude-Code-CLI/headless.md#start-faster-with-bare-mode) by passing `--bare` through `extraArgs` or setting `CLAUDE_CODE_SIMPLE=1`, recording stays off unless you set `snapshot: true`. Recording an `append` or custom prompt by default requires Claude Code v2.1.265 or later, which the TypeScript Agent SDK bundles from v0.3.265 and the Python Agent SDK from v0.2.153. Before Claude Code v2.1.268, sessions that don’t [fetch feature flags](../02-Claude-Code-CLI/env-vars.md#features-that-need-feature-flag-fetching), including sessions on Amazon Bedrock, Google Cloud’s Agent Platform, and Microsoft Foundry, rebuilt the prompt on every request and `snapshot` had no effect.


[​](#context-claude-code-adds-outside-the-system-prompt)

Context Claude Code adds outside the system prompt

System reminders are messages Claude Code adds to the conversation during a session to give Claude context, such as the contents of your CLAUDE.md files or a note that a file changed on disk. Claude Code sends them in the conversation, not in the system prompt, so they reach Claude whether you use the `claude_code` preset or pass your own string as `systemPrompt`. This section covers the [reminders most likely to change how your agent behaves](#reminders-claude-code-adds-to-the-conversation), how to [turn off the ones your agent replaces](#turn-off-the-context-your-agent-replaces), and how to [see what Claude received](#see-what-claude-received) in a specific request.


[​](#reminders-claude-code-adds-to-the-conversation)

Reminders Claude Code adds to the conversation

System reminders are text Claude Code adds to the conversation alongside the prompts your code sends. The following reminders are the ones most likely to change how your agent behaves:

- **Project instructions**: the CLAUDE.md files that your [`settingSources`](#claude-md-files-for-project-level-instructions) option loads
- **Output style instructions**: the instructions of the active [output style](#output-styles-for-persistent-configurations), in the main conversation
- **Commit and pull request attribution**: the `Co-Authored-By` trailer and pull request footer from the [`attribution`](../02-Claude-Code-CLI/settings-reference.md#attribution) setting
- **Hook output**: text your [hooks](agent-sdk-hooks.md#outputs) return as `additionalContext`
- **Available skills**: the names and descriptions of the [skills](agent-sdk-skills.md) Claude can call
- **Available subagents**: the names and descriptions of the [subagents](agent-sdk-subagents.md) Claude can start
- **Task list nudges**: in a [session that has the task-tracking tools](agent-sdk-todo-tracking.md#model-availability), a prompt to update the task list when Claude hasn’t touched it for several turns
- **File-changed notes**: a note that a file Claude read earlier has changed on disk

Claude Code introduces your CLAUDE.md files with a line telling Claude that the instructions override default behavior. If you pass your own string as `systemPrompt`, add a sentence to it that says what a system reminder is. The `claude_code` preset has one, and your string replaces the whole preset. Without it, nothing in your prompt tells Claude that reminders such as CLAUDE.md content and hook output come from the application rather than the user. For example:

```python
The application adds system reminders to this conversation. Treat them as context from the application, not as messages from the user.
```


[​](#turn-off-the-context-your-agent-replaces)

Turn off the context your agent replaces

Turn off a piece of built-in context when your agent supplies its own version of the same guidance. For example, if your prompt tells Claude to write commit messages as `PROJ-142: fix login redirect` with no trailers, Claude Code still tells Claude to end each commit message with a `Co-Authored-By` trailer, so Claude receives two conflicting instructions for the same commit. Pass settings keys through the [`settings`](agent-sdk-typescript.md#options) option in TypeScript or [`settings`](agent-sdk-python.md#claudeagentoptions) in Python, and environment variables through the `env` option. In TypeScript, [`env`](agent-sdk-typescript.md#options) replaces the inherited environment, so spread `process.env` into it.

| Built-in context                                                              | How to turn it off                                                                                                                                                                                 |
|:------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| The built-in commit and pull request instructions and the git status snapshot | Set [`includeGitInstructions`](../02-Claude-Code-CLI/settings-reference.md#includegitinstructions) to `false`, or `CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1`                                                         |
| The `Co-Authored-By` trailer and the pull request footer                      | Set [`attribution.commit`](../02-Claude-Code-CLI/settings-reference.md#attribution-commit) and [`attribution.pr`](../02-Claude-Code-CLI/settings-reference.md#attribution-pr) to your own text, or to empty strings to remove them |
| The user or project settings source, including its CLAUDE.md                  | Leave `'user'` or `'project'` out of [`settingSources`](agent-sdk-claude-code-features.md#control-filesystem-settings-with-settingsources)                                                   |
| Every CLAUDE.md file                                                          | Set `CLAUDE_CODE_DISABLE_CLAUDE_MDS=1`                                                                                                                                                             |
| Task list nudges, file-changed notes, and the skill list                      | Set `CLAUDE_CODE_DISABLE_ATTACHMENTS=1`                                                                                                                                                            |

Claude Code’s built-in commit and pull request instructions aren’t a reminder. They are part of the Bash tool’s description, so they also reach Claude when you pass a custom `systemPrompt`. If you set `CLAUDE_CODE_DISABLE_ATTACHMENTS`, Claude Code also sends `@` file mentions as plain text instead of expanding them into file content. The list of available subagents and background task notifications still arrive. The following example is for an agent that carries its own commit rules in `append`. It sets both `attribution` keys to empty strings to remove the trailer and footer, and turns off `includeGitInstructions` so Claude Code’s own commit workflow instructions don’t compete with yours:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Commit the staged changes for ticket PROJ-142",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code",
      append: "Write commit messages as: <ticket id>: <summary>. Add no trailers."
    },
    settings: {
      includeGitInstructions: false,
      attribution: { commit: "", pr: "" }
    },
    allowedTools: ["Bash(git *)"]
  }
})) {
  if (message.type === "result") console.log(message.subtype);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Commit the staged changes for ticket PROJ-142",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": "Write commit messages as: <ticket id>: <summary>. Add no trailers.",
            },
            settings='{"includeGitInstructions": false, "attribution": {"commit": "", "pr": ""}}',
            allowed_tools=["Bash(git *)"],
        ),
    ):
        print(message)


asyncio.run(main())
```

To confirm the change, run the example in a repository with staged changes and check the new commit with `git log -1`. The message ends without a `Co-Authored-By` trailer.


[​](#see-what-claude-received)

See what Claude received

The SDK message stream doesn’t include system reminders, so reading the messages your code receives won’t show you what Claude saw. To see them, log the requests Claude Code sends:

- **Raw request logging**: set [`OTEL_LOG_RAW_API_BODIES`](../13-Enterprise-Admin/monitoring-usage.md#api-request-body-event) to `file:<dir>`. Claude Code writes each request body to that directory.
- **A gateway you control**: point [`ANTHROPIC_BASE_URL`](../13-Enterprise-Admin/llm-gateway.md) at a proxy that logs request bodies.

In a logged request, look in the `messages` array. A reminder appears inside a user message wrapped in `<system-reminder>` tags or, on some models, as a separate message with the `system` role.


[​](#compare-the-four-approaches)

Compare the four approaches

The four customization methods differ in where they live, how they’re shared, and what they preserve from the `claude_code` preset.

| Feature                 | CLAUDE.md        | Output Styles                           | `systemPrompt` with append | Custom `systemPrompt`  |
|-------------------------|------------------|-----------------------------------------|----------------------------|------------------------|
| **Persistence**         | Per-project file | Saved as files                          | Session only               | Session only           |
| **Reusability**         | Per-project      | Across projects                         | Code duplication           | Code duplication       |
| **Management**          | On filesystem    | CLI + files                             | In code                    | In code                |
| **Default tools**       | Preserved        | Preserved                               | Preserved                  | Lost (unless included) |
| **Built-in safety**     | Maintained       | Maintained                              | Maintained                 | Must be added          |
| **Customization level** | Additions only   | Additions; can omit coding instructions | Additions only             | Complete control       |
| **Version control**     | With project     | Yes                                     | With code                  | With code              |
| **Scope**               | Project-specific | User or project                         | Code session               | Code session           |

“With append” means using `systemPrompt: { type: "preset", preset: "claude_code", append: "..." }` in TypeScript or `system_prompt={"type": "preset", "preset": "claude_code", "append": "..."}` in Python. CLAUDE.md doesn’t change the system prompt itself: the SDK injects its content into the conversation as project context.


[​](#combine-approaches)

Combine approaches

The approaches compose. A persistent output style or CLAUDE.md sets the long-lived behavior, and `append` layers session-specific instructions on top without touching the saved configuration.


[​](#combine-an-output-style-with-session-specific-additions)

Combine an output style with session-specific additions

The example below assumes a Code Reviewer output style is already active. The `append` block layers session-specific focus areas on top of the persona, so a single review session can prioritize OAuth and token storage without changing the saved output style:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Assuming "Code Reviewer" output style is active (via /config or settings)
// Add session-specific focus areas
const messages = [];

for await (const message of query({
  prompt: "Review this authentication module",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code",
      append: `
        For this review, prioritize:
        - OAuth 2.0 compliance
        - Token storage security
        - Session management
      `
    }
  }
})) {
  messages.push(message);
}
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

# Assuming "Code Reviewer" output style is active (via /config or settings)
# Add session-specific focus areas
messages = []


async def main():
    async for message in query(
        prompt="Review this authentication module",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": """
                For this review, prioritize:
                - OAuth 2.0 compliance
                - Token storage security
                - Session management
                """,
            }
        ),
    ):
        messages.append(message)


asyncio.run(main())
```


[​](#see-also)

See also

- [Output styles](../02-Claude-Code-CLI/output-styles.md): create, manage, and share output styles for the CLI, including the file format and storage locations
- [How Claude remembers your project](../02-Claude-Code-CLI/memory.md): what to put in CLAUDE.md, where to place it, and how to write effective project instructions
- [TypeScript SDK reference](agent-sdk-typescript.md): the full `Options` type, including `systemPrompt`, `settingSources`, and `settings`
- [Python SDK reference](agent-sdk-python.md): the full `ClaudeAgentOptions` type, including `system_prompt` and `setting_sources`
- [Settings](../02-Claude-Code-CLI/settings.md): the `settings.json` reference, including where output styles and other configuration are stored

---

## Observability with OpenTelemetry - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/observability
Path: 05-Agent-SDK/agent-sdk-observability.md

## On this page

- [How telemetry flows from the SDK](#how-telemetry-flows-from-the-sdk)
- [Enable telemetry export](#enable-telemetry-export)
  - [Flush telemetry from short-lived calls](#flush-telemetry-from-short-lived-calls)
- [Read agent traces](#read-agent-traces)
- [Link traces to your application](#link-traces-to-your-application)
- [Tag telemetry from your agent](#tag-telemetry-from-your-agent)
- [Attribute actions to your end users](#attribute-actions-to-your-end-users)
- [Control sensitive data in exports](#control-sensitive-data-in-exports)
- [Related documentation](#related-documentation)

Control and observability

# Observability with OpenTelemetry

Copy pageCopy page

Export traces, metrics, and events from the Agent SDK to your observability backend using OpenTelemetry.

Copy pageCopy page

When you run agents in production, you need visibility into what they did:

- which tools they called
- how long each model request took
- how many tokens were spent
- where failures occurred

The Agent SDK can export this data as OpenTelemetry traces, metrics, and log events to any backend that accepts the OpenTelemetry Protocol (OTLP), whether a hosted observability platform or a self-hosted collector. This guide explains how the SDK emits telemetry, how to configure the export, and how to tag and filter the data once it reaches your backend. To read token usage and cost directly from the SDK response stream instead of exporting to a backend, see [Track cost and usage](agent-sdk-cost-tracking.md).


[​](#how-telemetry-flows-from-the-sdk)

How telemetry flows from the SDK

The Agent SDK runs the Claude Code CLI as a child process and communicates with it over a local pipe. The CLI has OpenTelemetry instrumentation built in: it records spans around each model request and tool execution, emits metrics for token and cost counters, and emits structured log events for prompts and tool results. The SDK does not produce telemetry of its own. Instead, it passes configuration through to the CLI process, and the CLI exports directly to your collector. Configuration is passed as environment variables. By default, the child process inherits your application’s environment, so you can configure telemetry in either of two places:

- **Process environment:** set the variables in your shell, container, or orchestrator before your application starts. Every `query()` call picks them up automatically with no code change. This is the recommended approach for production deployments.
- **Per-call options:** set the variables in `ClaudeAgentOptions.env` (Python) or `options.env` (TypeScript). Use this when different agents in the same process need different telemetry settings. In Python, `env` is merged on top of the inherited environment. In TypeScript, `env` replaces the inherited environment entirely, so include `...process.env` in the object you pass.

The CLI exports three independent OpenTelemetry signals. Each has its own enable switch and its own exporter, so you can turn on only the ones you need.

| Signal     | What it contains                                                            | Enable with                                                         |
|------------|-----------------------------------------------------------------------------|---------------------------------------------------------------------|
| Metrics    | Counters for tokens, cost, sessions, lines of code, and tool decisions      | `OTEL_METRICS_EXPORTER`                                             |
| Log events | Structured records for each prompt, API request, API error, and tool result | `OTEL_LOGS_EXPORTER`                                                |
| Traces     | Spans for each interaction, model request, tool call, and hook (beta)       | `OTEL_TRACES_EXPORTER` plus `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` |

For the complete list of metric names, event names, and attributes, see the Claude Code [Monitoring](../13-Enterprise-Admin/monitoring-usage.md) reference. The Agent SDK emits the same data because it runs the same CLI. Span names are listed in [Read agent traces](#read-agent-traces) below.


[​](#enable-telemetry-export)

Enable telemetry export

Telemetry is off until you set `CLAUDE_CODE_ENABLE_TELEMETRY=1` and choose at least one exporter. The most common configuration sends all three signals over OTLP HTTP to a collector. The following example sets the variables in a dictionary and passes them through `options.env`. The agent runs a single task, and the CLI exports spans, metrics, and events to the collector at `collector.example.com` while the loop consumes the response stream:

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

OTEL_ENV = {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    # Required for traces, which are in beta. Metrics and log events do not need this.
    "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
    # Choose an exporter per signal. Use otlp for the SDK; see the Note below.
    "OTEL_TRACES_EXPORTER": "otlp",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    # Standard OTLP transport configuration.
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-token",
}


async def main():
    options = ClaudeAgentOptions(env=OTEL_ENV)
    async for message in query(
        prompt="List the files in this directory", options=options
    ):
        print(message)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const otelEnv = {
  CLAUDE_CODE_ENABLE_TELEMETRY: "1",
  // Required for traces, which are in beta. Metrics and log events do not need this.
  CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
  // Choose an exporter per signal. Use otlp for the SDK; see the Note below.
  OTEL_TRACES_EXPORTER: "otlp",
  OTEL_METRICS_EXPORTER: "otlp",
  OTEL_LOGS_EXPORTER: "otlp",
  // Standard OTLP transport configuration.
  OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
  OTEL_EXPORTER_OTLP_ENDPOINT: "http://collector.example.com:4318",
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer your-token",
};

for await (const message of query({
  prompt: "List the files in this directory",
  // env replaces the inherited environment in TypeScript, so spread
  // process.env first to keep PATH, ANTHROPIC_API_KEY, and other variables.
  options: { env: { ...process.env, ...otelEnv } },
})) {
  console.log(message);
}
```

Because the child process inherits your application’s environment by default, you can achieve the same result by exporting these variables in a Dockerfile, Kubernetes manifest, or shell profile and omitting `options.env` entirely. To confirm that export is working, check your collector’s logs for incoming spans, metrics, and log events after the task completes. The CLI fails silently on export errors by default: if the endpoint is unreachable or rejects the data, the agent still runs normally and the CLI drops the telemetry without surfacing an error in your application. To surface exporter errors, set [`CLAUDE_CODE_OTEL_DIAG_STDERR=1`](../02-Claude-Code-CLI/env-vars.md) alongside the exporter variables and read the diagnostics through the SDK’s `stderr` callback (Python) or `stderr` option (TypeScript). Requires Claude Code v2.1.179 or later.

The `console` exporter writes telemetry to standard output, which the SDK uses as its message channel. Do not set `console` as an exporter value when running through the SDK. To inspect telemetry locally, point `OTEL_EXPORTER_OTLP_ENDPOINT` at a local OpenTelemetry Collector instead.


[​](#flush-telemetry-from-short-lived-calls)

Flush telemetry from short-lived calls

The CLI batches telemetry and exports on an interval. On a clean process exit it attempts to flush pending data, but the flush is bounded by a short timeout, so spans can still be dropped if the collector is slow to respond. If your process is killed before the CLI shuts down, anything still in the batch buffer is lost. Lowering the export intervals reduces both windows. By default, metrics export every 60 seconds and traces and logs export every 5 seconds. The following example shortens all three intervals so that data reaches the collector while a short task is still running:

Python

TypeScript

```python
OTEL_ENV = {
    # ... exporter configuration from the previous example ...
    "OTEL_METRIC_EXPORT_INTERVAL": "1000",
    "OTEL_LOGS_EXPORT_INTERVAL": "1000",
    "OTEL_TRACES_EXPORT_INTERVAL": "1000",
}
```

```python
const otelEnv = {
  // ... exporter configuration from the previous example ...
  OTEL_METRIC_EXPORT_INTERVAL: "1000",
  OTEL_LOGS_EXPORT_INTERVAL: "1000",
  OTEL_TRACES_EXPORT_INTERVAL: "1000",
};
```


[​](#read-agent-traces)

Read agent traces

Traces give you the most detailed view of an agent run. With `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` set, each step of the agent loop becomes a span you can inspect in your tracing backend:

- **`claude_code.interaction`:** wraps a single turn of the agent loop, from receiving a prompt to producing a response.
- **`claude_code.llm_request`:** wraps each call to the Claude API, with model name, latency, and token counts as attributes.
- **`claude_code.tool`:** wraps each tool invocation, with child spans for the permission wait (`claude_code.tool.blocked_on_user`) and the execution itself (`claude_code.tool.execution`).
- **`claude_code.hook`:** wraps each [hook](agent-sdk-hooks.md) execution. Requires detailed beta tracing (`ENABLE_BETA_TRACING_DETAILED=1` and `BETA_TRACING_ENDPOINT`), a pair that also [changes where your logs and traces go](../02-Claude-Code-CLI/env-vars.md#variables).

The `llm_request`, `tool`, and `hook` spans are children of the enclosing `claude_code.interaction` span. When the agent spawns a subagent through the Agent tool, the subagent’s `llm_request` and `tool` spans nest under the parent agent’s `claude_code.tool` span, so the full delegation chain appears as one trace. Spans carry a `session.id` attribute by default. When you make several `query()` calls against the same [session](agent-sdk-sessions.md), filter on `session.id` in your backend to see them as one timeline. Claude Code omits the attribute if you set `OTEL_METRICS_INCLUDE_SESSION_ID` to a falsy value.

Tracing is in beta. Span names and attributes may change between releases. See [Traces (beta)](../13-Enterprise-Admin/monitoring-usage.md#traces-beta) in the Monitoring reference for the trace exporter configuration variables.


[​](#link-traces-to-your-application)

Link traces to your application

The SDK automatically propagates W3C trace context into the CLI subprocess. When you call `query()` while an OpenTelemetry span is active in your application, the SDK injects `TRACEPARENT` and `TRACESTATE` into the child process environment, and the CLI reads them so its `claude_code.interaction` span becomes a child of your span. The agent run then appears inside your application’s trace instead of as a disconnected root. OTLP event log records emitted during the run carry the same trace context: with `TRACEPARENT` set, each record’s `trace_id` and `span_id` match your application’s trace, so you can join [events](../13-Enterprise-Admin/monitoring-usage.md#events) to spans in your backend. Before v2.1.212, event records emitted outside an active span didn’t carry `trace_id` or `span_id`. When trace-context propagation is enabled, the CLI also forwards `TRACEPARENT` to every Bash and PowerShell command it runs. If a command launched through the Bash tool emits its own OpenTelemetry spans, those spans nest under the `claude_code.tool.execution` span that wraps the command. Auto-injection is skipped when you set `TRACEPARENT` explicitly in `options.env`, so you can pin a specific parent context if needed. Interactive CLI sessions ignore inbound `TRACEPARENT` entirely; only Agent SDK and `claude -p` runs honor it. See [Traces (beta)](../13-Enterprise-Admin/monitoring-usage.md#traces-beta) in the Monitoring reference for the full span and attribute reference.


[​](#tag-telemetry-from-your-agent)

Tag telemetry from your agent

By default, the CLI reports `service.name` as `claude-code`. If you run several agents, or run the SDK alongside other services that export to the same collector, override the service name and add resource attributes so you can filter by agent in your backend. The following example renames the service and attaches deployment metadata. These values are applied as OpenTelemetry resource attributes on every span, metric, and event the agent emits:

Python

TypeScript

```python
options = ClaudeAgentOptions(
    env={
        # ... exporter configuration from the Enable telemetry export example ...
        "OTEL_SERVICE_NAME": "support-triage-agent",
        "OTEL_RESOURCE_ATTRIBUTES": "service.version=1.4.0,deployment.environment=production",
    },
)
```

```python
const options = {
  env: {
    ...process.env,
    // ... exporter configuration from the Enable telemetry export example ...
    OTEL_SERVICE_NAME: "support-triage-agent",
    OTEL_RESOURCE_ATTRIBUTES:
      "service.version=1.4.0,deployment.environment=production",
  },
};
```


[​](#attribute-actions-to-your-end-users)

Attribute actions to your end users

The CLI attaches [identity attributes](../13-Enterprise-Admin/monitoring-usage.md#standard-attributes) to every event based on the credential it uses to call Anthropic. When you build an application that serves many end users from one deployment, these attributes identify your service’s credential, not the end user on whose behalf the agent acted. To make tool calls and MCP activity attributable to your application’s end users, inject end-user identity as resource attributes on each `query()` call. Percent-encode values before interpolating them, since `OTEL_RESOURCE_ATTRIBUTES` [reserves commas, spaces, and equals signs](../13-Enterprise-Admin/monitoring-usage.md#multi-team-organization-support). The following example attaches the requesting user and tenant to every span and event from one request. It assumes a `request` object from your web framework carrying the user and tenant IDs:

Python

TypeScript

```python
from urllib.parse import quote

options = ClaudeAgentOptions(
    env={
        # ... exporter configuration from the Enable telemetry export example ...
        # request is the incoming request object from your web framework.
        "OTEL_RESOURCE_ATTRIBUTES": f"enduser.id={quote(request.user_id)},tenant.id={quote(request.tenant_id)}",
    },
)
```

```python
const options = {
  env: {
    ...process.env,
    // ... exporter configuration from the Enable telemetry export example ...
    // request is the incoming request object from your web framework.
    OTEL_RESOURCE_ATTRIBUTES: `enduser.id=${encodeURIComponent(request.userId)},tenant.id=${encodeURIComponent(request.tenantId)}`,
  },
};
```

With end-user identity attached, the `tool_decision`, `tool_result`, `mcp_server_connection`, and `permission_mode_changed` events, which export as log records named with a `claude_code.` prefix, become a per-user audit trail you can forward to a Security Information and Event Management (SIEM) platform. See [Audit security events](../13-Enterprise-Admin/monitoring-usage.md#audit-security-events) in the Monitoring reference for the full list of security-relevant events and the attributes each one carries.


[​](#control-sensitive-data-in-exports)

Control sensitive data in exports

Telemetry is structural by default. Durations, model names, and tool names are recorded on every span; token counts are recorded when the underlying API request returns usage data, so spans for failed or aborted requests may omit them. The content your agent reads and writes is not recorded by default. These opt-in variables add content to the exported data:

| Variable                  | Adds                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `OTEL_LOG_USER_PROMPTS=1` | Prompt text on `claude_code.user_prompt` events and on the `claude_code.interaction` span                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `OTEL_LOG_TOOL_DETAILS=1` | Tool input arguments such as file paths, shell commands, and search patterns on `claude_code.tool_result` events, and real agent, skill, plugin, and MCP server names on the [cost and token metrics](../13-Enterprise-Admin/monitoring-usage.md#cost-counter)                                                                                                                                                                                                                                                                                                                                                    |
| `OTEL_LOG_TOOL_CONTENT=1` | A [`tool.output` span event](../13-Enterprise-Admin/monitoring-usage.md#tool-output-span-event) on `claude_code.tool` with file contents, Bash output, and what MCP tools, WebFetch, and WebSearch return, truncated at 60 KB by default, configurable via `CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH`, which requires Claude Code v2.1.214 or later. Results from MCP tools, WebFetch, and WebSearch require Claude Code v2.1.283 or later. Requires [tracing](#read-agent-traces) to be enabled. Span attributes carry tool content under [their own gates](../13-Enterprise-Admin/monitoring-usage.md#new-context-gates)              |
| `OTEL_LOG_RAW_API_BODIES` | Full Anthropic Messages API request and response JSON as `claude_code.api_request_body` and `claude_code.api_response_body` log events. Set to `1` for inline bodies truncated at 60 KB by default, or `file:<dir>` for untruncated bodies on disk with a `body_ref` path in the event. `CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH` configures the inline truncation limit, and requires Claude Code v2.1.214 or later. Bodies include the entire conversation history and have extended-thinking content redacted. Enabling this implies consent to everything the three variables above would reveal |

Leave these unset unless your observability pipeline is approved to store the data your agent handles. See [Security and privacy](../13-Enterprise-Admin/monitoring-usage.md#security-and-privacy) in the Monitoring reference for the full list of attributes and redaction behavior.


[​](#related-documentation)

Related documentation

These guides cover adjacent topics for monitoring and deploying agents:

- [Track cost and usage](agent-sdk-cost-tracking.md): read token and cost data from the message stream without an external backend.
- [Hosting the Agent SDK](agent-sdk-hosting.md): deploy agents in containers where you can set OpenTelemetry variables at the environment level.
- [Monitoring](../13-Enterprise-Admin/monitoring-usage.md): the complete reference for every environment variable, metric, and event the CLI emits.

---

## Agent SDK overview - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/overview
Path: 05-Agent-SDK/agent-sdk-overview.md

## On this page

- [Compare the Agent SDK to other Claude tools](#compare-the-agent-sdk-to-other-claude-tools)
- [Capabilities](#capabilities)
- [Get started](#get-started)
- [Changelog](#changelog)
- [Report bugs](#report-bugs)
- [Branding guidelines](#branding-guidelines)
- [License and terms](#license-and-terms)
- [Next steps](#next-steps)

Agent SDK

# Agent SDK overview

Copy pageCopy page

Build production AI agents with Claude Code as a library

Copy pageCopy page

An agent is an application that completes a task by planning its own steps and calling tools that read files, run commands, or edit code. The Agent SDK gives you the same tools, [agent loop](agent-sdk-agent-loop.md), and context management that power Claude Code, programmable in Python and TypeScript.


[​](#compare-the-agent-sdk-to-other-claude-tools)

Compare the Agent SDK to other Claude tools

The Agent SDK, the CLI, the Client SDK, and Managed Agents differ in who runs the agent, what comes built in, and how you reach it. Find the row that matches how you want to build and run yours.

| You want to                                                                                      | Use                                                                               | What you get                                                                                                                                                                                                                                                                                                                                                                                  |
|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Embed Claude Code’s agent in your own Python or TypeScript application, in a process you operate | **Agent SDK**                                                                     | A library that runs the Claude Code binary, with Claude Code’s [capabilities](#capabilities), such as built-in tools, permissions, sessions, and hooks.                                                                                                                                                                                                                                       |
| Do interactive development or run one-off tasks from a terminal                                  | [**Claude Code CLI**](../02-Claude-Code-CLI/code-home.md)                                          | The terminal interface, built for daily interactive use.                                                                                                                                                                                                                                                                                                                                      |
| Call the Claude API directly from your own code                                                  | [**Client SDK**](../04-API-Reference/Other/cli-sdks-libraries-overview.md) | Direct access to the Claude API from any of the client SDK languages. You write the tool loop yourself, or let the client SDK’s beta [tool runner](https://code.claude.com/docs/en/04-API-Reference/Agents-Tools/agents-and-tools-tool-use-tool-runner-8ab76faf78.md) drive it.                                                                                                                                                       |
| Have Anthropic host the agent, configured through the Claude API                                 | [**Managed Agents**](https://code.claude.com/docs/en/04-API-Reference/Other/managed-agents-overview-b12cd03916.md) | A hosted agent harness that runs the agent loop, with sessions in an Anthropic-managed cloud sandbox or a [self-hosted sandbox](../04-API-Reference/Other/managed-agents-self-hosted-sandboxes.md) on your own infrastructure. Use it from the [SDK for your language](https://code.claude.com/docs/en/04-API-Reference/Other/managed-agents-quickstart-841ac0c596.md#install-the-sdk), the `ant` CLI, or the REST API. |

To drive the same agent loop from a language other than Python or TypeScript, [run the CLI as a subprocess](../02-Claude-Code-CLI/headless.md) with the `-p` flag and `--output-format json`.


[​](#capabilities)

Capabilities

These Claude Code capabilities are available in the SDK:

| Capability                   | What it does                                                                                 | Learn more                                                                                                                                                                                                                         |
|------------------------------|----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Built-in tools               | Read, write, edit files, run commands, and search the web                                    | [Tools reference](../02-Claude-Code-CLI/tools-reference.md)                                                                                                                                                                                        |
| Hooks                        | Run custom code at key points in the agent lifecycle                                         | [Hooks](agent-sdk-hooks.md)                                                                                                                                                                                                  |
| Subagents                    | Spawn specialized agents for focused subtasks                                                | [Subagents](agent-sdk-subagents.md)                                                                                                                                                                                          |
| MCP                          | Connect external tools and data sources via the Model Context Protocol                       | [MCP](agent-sdk-mcp.md)                                                                                                                                                                                                      |
| Permissions                  | Control which tools run automatically, which need approval                                   | [Permissions](agent-sdk-permissions.md)                                                                                                                                                                                      |
| Sessions                     | Maintain context across exchanges, resume or fork later                                      | [Sessions](agent-sdk-sessions.md)                                                                                                                                                                                            |
| Skills, commands, and memory | Load automatically from your project’s `.claude/` and from `~/.claude/`, same as Claude Code | [Skills](agent-sdk-skills.md), [Commands](agent-sdk-skills.md#commands-in-agent-sdk-sessions), [Memory](agent-sdk-modifying-system-prompts.md), [Configuration loading](agent-sdk-claude-code-features.md) |
| Plugins                      | Package skills, agents, hooks, and MCP servers, and load them by local path                  | [Plugins](agent-sdk-plugins.md)                                                                                                                                                                                              |


[​](#get-started)

Get started

Follow the [Quickstart](agent-sdk-quickstart.md) to install the SDK, set your API key, and build your first agent, one that finds and fixes bugs in existing code.

Unless previously approved, Anthropic does not allow third party developers to offer claude.ai login or rate limits for their products, including agents built on the Claude Agent SDK. Use the API key authentication methods described in the [Quickstart](agent-sdk-quickstart.md) instead.


[​](#changelog)

Changelog

View the full changelog for SDK updates, bug fixes, and new features:

- **TypeScript SDK**: [view CHANGELOG.md](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md)
- **Python SDK**: [view CHANGELOG.md](https://github.com/anthropics/claude-agent-sdk-python/blob/main/CHANGELOG.md)


[​](#report-bugs)

Report bugs

If you encounter bugs or issues with the Agent SDK:

- **TypeScript SDK**: [report issues on GitHub](https://github.com/anthropics/claude-agent-sdk-typescript/issues)
- **Python SDK**: [report issues on GitHub](https://github.com/anthropics/claude-agent-sdk-python/issues)


[​](#branding-guidelines)

Branding guidelines

For partners integrating the Claude Agent SDK, use of Claude branding is optional. When referencing Claude in your product: **Allowed:**

- “Claude Agent”, preferred for dropdown menus
- “Claude”, when within a menu already labeled “Agents”
- “{YourAgentName} Powered by Claude”, if you have an existing agent name

**Not permitted:**

- “Claude Code” or “Claude Code Agent”
- Claude Code-branded ASCII art or visual elements that mimic Claude Code

Your product should maintain its own branding and not appear to be Claude Code or any Anthropic product. For questions about branding compliance, contact the Anthropic [sales team](https://www.anthropic.com/contact-sales).


[​](#license-and-terms)

License and terms

Use of the Claude Agent SDK is governed by [Anthropic’s Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms), including when you use it to power products and services that you make available to your own customers and end users, except to the extent a specific component or dependency is covered by a different license as indicated in that component’s LICENSE file.


[​](#next-steps)

Next steps

These resources cover deeper technical detail and example projects for building with the Agent SDK.

- [Quickstart](agent-sdk-quickstart.md): build your first agent that finds and fixes bugs
- [Migration guide](agent-sdk-migration-guide.md): migrate from the Claude Code SDK packages to the Agent SDK
- [Agent loop](agent-sdk-agent-loop.md): how Claude plans, calls tools, and decides when a task is done
- [Example agents](https://github.com/anthropics/claude-agent-sdk-demos): demo apps for local development
- [TypeScript SDK](agent-sdk-typescript.md): full TypeScript API reference and examples
- [Python SDK](agent-sdk-python.md): full Python API reference and examples
- [Agent harness design](https://claude.com/blog/a-harness-for-every-task-dynamic-workflows-in-claude-code): how the Claude Code team uses dynamic workflows to orchestrate many subagents at once

---

## Configure permissions - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/permissions
Path: 05-Agent-SDK/agent-sdk-permissions.md

## On this page

- [How permissions are evaluated](#how-permissions-are-evaluated)
- [Allow and deny rules](#allow-and-deny-rules)
- [Permission modes](#permission-modes)
  - [Available modes](#available-modes)
  - [Set permission mode](#set-permission-mode)
  - [Mode details](#mode-details)
  - [Accept edits mode (acceptEdits)](#accept-edits-mode-acceptedits)
  - [Don’t ask mode (dontAsk)](#don%E2%80%99t-ask-mode-dontask)
  - [Bypass permissions mode (bypassPermissions)](#bypass-permissions-mode-bypasspermissions)
  - [Plan mode (plan)](#plan-mode-plan)
- [Related resources](#related-resources)

Control and observability

# Configure permissions

Copy pageCopy page

Control how your agent uses tools with permission modes, hooks, and declarative allow/deny rules.

Copy pageCopy page

The Claude Agent SDK provides permission controls to manage how Claude uses tools. Use permission modes and rules to define what’s allowed automatically, and the [`canUseTool` callback](agent-sdk-user-input.md) to handle everything else at runtime.


[​](#how-permissions-are-evaluated)

How permissions are evaluated

When Claude requests a tool, the SDK checks permissions in this order:

1

Hooks

Run [hooks](agent-sdk-hooks.md) first. A hook can deny the call outright or pass it on. A hook that returns `allow` does not skip the deny and ask rules below; those are evaluated regardless of the hook result. A `PreToolUse` hook allow also can’t approve an `rm` or `rmdir` removal targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths).

2

Deny rules

Check `deny` rules (from `disallowed_tools` and [settings.json](../02-Claude-Code-CLI/settings-reference.md#permission-settings)). If a deny rule matches, the tool is blocked, even in `bypassPermissions` mode. Bare-name deny rules like `Bash` remove the tool from Claude’s context before this evaluation begins, so only scoped rules like `Bash(rm *)` are checked at this step.

3

Ask rules

Check `ask` rules from [settings.json](../02-Claude-Code-CLI/settings-reference.md#permission-settings). If an ask rule matches, the call falls through to your [`canUseTool` callback](agent-sdk-user-input.md) for confirmation, even in `bypassPermissions` mode.Tools that require user interaction behave the same way: `AskUserQuestion` and MCP tools whose server sets [`_meta["anthropic/requiresUserInteraction"]`](../06-MCP-Tools/General/mcp.md#require-approval-for-a-specific-tool) always fall through to the callback, even when an allow rule matches. In `dontAsk` mode both cases are denied instead, because that mode never prompts. The MCP annotation requires Claude Code v2.1.199 or later.[claude.ai connector](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools) tools your organization has set to `ask` also leave the flow at this step. Every call falls through to the callback, even in `bypassPermissions` mode and even when an allow rule matches. The callback receives the reason `Your organization requires approval for this tool`. In `dontAsk` mode the call is denied instead, because that mode never prompts.

4

Permission mode

Apply the active [permission mode](#permission-modes):

- In `bypassPermissions` mode, Claude Code approves everything that reaches this step except `rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths), which fall through instead.
- In `acceptEdits` mode, Claude Code approves the file operations listed under [Accept edits mode](#accept-edits-mode-acceptedits).
- In `plan` mode, Claude Code sends file-edit and shell-write tools to your `canUseTool` callback regardless of allow rules, so write operations can’t be auto-approved while planning.
- In other modes, the request falls through.

5

Allow rules

Check `allow` rules (from `allowed_tools` and settings.json). If a rule matches, the tool is approved. A call the tool approves on its own is resolved at this step too, with no rule needed: for example a file read inside your working directories or a [read-only Bash command](../02-Claude-Code-CLI/permissions.md#read-only-commands).`rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths) are never approved by an allow rule. Whether they then reach your callback depends on the permission mode: in an Agent SDK session in `auto` mode, for example, Claude Code denies them by default without calling it. The [Critical paths](../02-Claude-Code-CLI/permission-modes.md#critical-paths) mode table lists what each mode does with them.

6

canUseTool callback

If not resolved by any of the above, call your [`canUseTool` callback](agent-sdk-user-input.md) for a decision. In `dontAsk` mode, this step is skipped and the tool is denied.In the TypeScript SDK, if you set [`permissionPrompts: 'none'`](agent-sdk-typescript.md#options), your callback isn’t called at this step. A [`PermissionRequest` hook](../07-Hooks/hooks.md#permissionrequest) still gets a chance to decide, and if it doesn’t, Claude Code denies the call. The option requires Claude Code v2.1.259 or later.

If you pass a `canUseTool` callback in a configuration where the TypeScript SDK expects the evaluation order to auto-approve calls before the callback is consulted, the SDK emits a Node.js process warning once when the query is constructed. The warning’s code is `CLAUDE_SDK_CAN_USE_TOOL_SHADOWED`. Two configurations trigger it:

- `permissionMode: 'bypassPermissions'`, which auto-approves every call that reaches the permission mode step apart from the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves)
- Each bare `allowedTools` entry such as `"Read"`, which auto-approves that whole tool before the callback is consulted, apart from the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves)

Entries with a specifier such as `Bash(ls *)` and the `acceptEdits` mode don’t trigger it, and allow rules coming from settings files aren’t visible to the check. Listen with `process.on('warning', ...)` and match the code to log or suppress it. To gate every tool call regardless of mode and rules, use a [`PreToolUse` hook](agent-sdk-hooks.md) instead. This page focuses on **allow and deny rules** and **permission modes**. For the other steps:

- **Hooks:** run custom code to allow, deny, or modify tool requests. See [Control execution with hooks](agent-sdk-hooks.md).
- **canUseTool callback:** prompt users for approval at runtime, when no earlier step resolves the call. See [Handle approvals and user input](agent-sdk-user-input.md).


[​](#allow-and-deny-rules)

Allow and deny rules

`allowed_tools` and `disallowed_tools` (TypeScript: `allowedTools` / `disallowedTools`) add entries to the allow and deny rule lists in the evaluation flow above. If you name one of the [task-tracking tools](agent-sdk-todo-tracking.md#model-availability) in `allowed_tools`, Claude Code also opts the session in. Any other tool not listed in `allowed_tools` is still available to Claude, and a call to it that needs approval falls through to the permission mode. Deny rules behave differently depending on whether they name a tool or scope a pattern within one.

| Option                            | Effect                                                                                                                                                                                                                                              |
|:----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allowed_tools=["Read", "Grep"]`  | `Read` and `Grep` are auto-approved. Other tools not listed here still exist, and calls to them that need approval fall through to the permission mode and `canUseTool`.                                                                            |
| `disallowed_tools=["Bash"]`       | The `Bash` tool definition is removed from the request. Claude does not see the tool and cannot attempt it.                                                                                                                                         |
| `disallowed_tools=["Bash(rm *)"]` | `Bash` stays available. Calls matching `rm *` [as written](../02-Claude-Code-CLI/permissions.md#bash-rule-limits) are denied in every permission mode, including `bypassPermissions`. Other `Bash` calls, including `/bin/rm`, fall through to the permission mode. |
| `disallowed_tools=["*"]`          | Every tool definition is removed from the request. Tool-name globs are supported in deny rules: `"*"` matches every tool and `"mcp__*"` matches every MCP tool across all servers.                                                                  |

Allow rules accept tool-name globs only after a literal `mcp__<server>__` prefix. The server segment must be glob-free so the rule names a specific server you configured: `mcp__puppeteer__*` matches every tool from the `puppeteer` server, and `mcp__github__get_*` matches its `get_` tools. An unanchored entry like `allowed_tools=["*"]` or `allowed_tools=["mcp__*"]` is ignored with a startup warning and does not auto-approve anything. Scoped rules for `Read` and `Edit` take a path pattern. `Edit(path)` rules govern all built-in tools that write files, including `Write` and `NotebookEdit`; a `Write(path)` rule is never matched by the file permission checks. Use `//path` for an absolute filesystem path: a deny rule of `Edit(//secrets/**)` blocks writes anywhere under `/secrets` on disk. With a single leading slash, `Edit(/secrets/**)` anchors at the rule’s source instead. For rules passed through `allowed_tools` or `disallowed_tools`, that means the session’s working directory, so the rule doesn’t block `/secrets` on disk. See [Read and Edit rules](../02-Claude-Code-CLI/permissions.md#read-and-edit) for the four anchor forms and how rules from settings files resolve.

**Auto-approved tools never reach `canUseTool`.** A tool call approved at any earlier step, by `acceptEdits` or `bypassPermissions`, or by an allow rule, skips your `canUseTool` callback, so permission checks you put there are silently bypassed for that tool.An allow rule never auto-approves `AskUserQuestion`, MCP tools marked [`_meta["anthropic/requiresUserInteraction"]`](../06-MCP-Tools/General/mcp.md#require-approval-for-a-specific-tool), connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools), or `rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths). In `dontAsk` mode Claude Code denies these calls without invoking the callback. In other modes the first three reach the callback. Depending on the [permission mode](../02-Claude-Code-CLI/permission-modes.md#critical-paths), a critical-path removal reaches the callback too or Claude Code denies it without calling it, as it does by default for an Agent SDK session in `auto` mode.Coverage depends on the entry’s form: a bare name like `Read` or `mcp__github__get_issue` auto-approves every call to that tool apart from the exceptions above, while a scoped rule like `Bash(npm test *)` auto-approves only matching calls, and other `Bash` calls that need approval still fall through to the callback. For checks that must run on every tool call, use a [`PreToolUse` hook](agent-sdk-hooks.md): hooks run before every other step, and a hook deny applies even in `bypassPermissions` mode.

For a locked-down agent, pair `allowedTools` with `permissionMode: "dontAsk"`:

```python
const options = {
  allowedTools: ["Read", "Glob", "Grep"],
  permissionMode: "dontAsk"
};
```

Listed tools are approved, apart from the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves), and every other call that would prompt is denied instead. Calls that need no approval in `default` mode run whether or not you list them, such as [read-only Bash commands](../02-Claude-Code-CLI/permissions.md#read-only-commands), tools like `Agent` that don’t ask before running, and file reads inside your working directories. To put a tool out of Claude’s reach entirely, add its bare name to `disallowedTools`.

**`allowed_tools` does not constrain `bypassPermissions`.** `allowed_tools` pre-approves the tools you list. Other unlisted tools are not matched by any allow rule and fall through to the permission mode, where `bypassPermissions` approves them. Setting `allowed_tools=["Read"]` alongside `permission_mode="bypassPermissions"` still approves every tool, including `Bash`, `Write`, and `Edit`. If you need `bypassPermissions` but want specific tools blocked, use `disallowed_tools`.

You can also configure allow, deny, and ask rules declaratively in `.claude/settings.json`. These rules are read when the `project` setting source is enabled, which it is for default `query()` options. If you set `setting_sources` (TypeScript: `settingSources`) explicitly, include `"project"` for them to apply. See [Permission settings](../02-Claude-Code-CLI/settings-reference.md#permission-settings) for the rule syntax.


[​](#permission-modes)

Permission modes

Permission modes provide global control over how Claude uses tools. You can set the permission mode when calling `query()` or change it dynamically during streaming sessions. If you don’t set one, Claude Code picks the starting permission mode by the rules in [Which mode a session starts in](../02-Claude-Code-CLI/permission-modes.md#which-mode-a-session-starts-in):

- A `permissions.defaultMode` from the session’s [settings files](../02-Claude-Code-CLI/settings.md#where-settings-live) when one applies
- Otherwise the built-in default, which can be [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode)

A session that starts in auto mode drops broad allow rules such as a bare `Bash` entry, as [How auto mode evaluates actions](../02-Claude-Code-CLI/permission-modes.md#how-auto-mode-evaluates-actions) describes. If your application relies on the `default` mode or on such a rule, pass `default` explicitly. Before TypeScript Agent SDK v0.3.286, omitting `permissionMode` was the same as passing `default`.


[​](#available-modes)

Available modes

The SDK supports these permission modes:

| Mode                | Description                  | Tool behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|:--------------------|:-----------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `default`           | Standard permission behavior | No mode-based auto-approvals; calls that need approval and match no allow rule trigger your `canUseTool` callback                                                                                                                                                                                                                                                                                                                                                                          |
| `dontAsk`           | Deny instead of prompting    | Any call that would otherwise prompt is denied. Calls approved by `allowed_tools` or rules run, and so do calls that need no approval in `default` mode; connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools) and tools that require user interaction are denied even if you’ve pre-approved them, as are `rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths). `canUseTool` is never called |
| `acceptEdits`       | Auto-accept file edits       | File edits and [filesystem operations](#accept-edits-mode-acceptedits) (`mkdir`, `rm`, `mv`, etc.) are automatically approved                                                                                                                                                                                                                                                                                                                                                              |
| `bypassPermissions` | Bypass permission checks     | Tools run without permission prompts, except for the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves). Use with caution                                                                                                                                                                                                                                                                                                                            |
| `plan`              | Planning mode                | Claude explores and plans without editing your source files; file edits are never auto-approved and prompt through your `canUseTool` callback                                                                                                                                                                                                                                                                                                                                              |
| `auto`              | Model-classified approvals   | A model classifier reviews actions such as shell commands and network requests, allowing or blocking each one it reviews. See [Auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) for availability and the decision order                                                                                                                                                                                                                                              |

**Subagent inheritance:** A subagent runs in the parent session’s permission mode unless you set `permissionMode` on its [`AgentDefinition`](agent-sdk-typescript.md#agentdefinition) and the parent session is in `default`, `dontAsk`, or `plan` mode. Even then, Claude Code never applies a `"bypassPermissions"` value, and applies an `"auto"` value only when [auto mode is available](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) to that subagent. A subagent runs in `bypassPermissions` mode only when the parent session itself does. The `bypassPermissions` exception requires Claude Code v2.1.267 or later.Subagents may have different system prompts and less constrained behavior than your main agent, so inheriting `bypassPermissions` grants them full, autonomous system access. The [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves) still apply.


[​](#set-permission-mode)

Set permission mode

You can set the permission mode once when starting a query, or change it dynamically while the session is active.

- At query time

- During streaming

Pass `permission_mode` (Python) or `permissionMode` (TypeScript) when creating a query. This mode applies for the entire session unless changed dynamically.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Help me refactor this code",
        options=ClaudeAgentOptions(
            permission_mode="default",  # Set the mode here
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "Help me refactor this code",
    options: {
      permissionMode: "default" // Set the mode here
    }
  })) {
    if ("result" in message) {
      console.log(message.result);
    }
  }
}

main();
```

Call `set_permission_mode()` (Python) or `setPermissionMode()` (TypeScript) to change the mode mid-session. The new mode takes effect immediately for all subsequent tool requests. This lets you start restrictive and loosen permissions as trust builds, for example switching to `acceptEdits` after reviewing Claude’s initial approach.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions


async def main():
    async with ClaudeSDKClient(
        options=ClaudeAgentOptions(
            permission_mode="default",  # Start in default mode
        )
    ) as client:
        await client.query("Help me refactor this code")

        # Change mode dynamically mid-session
        await client.set_permission_mode("acceptEdits")

        # Process messages with the new permission mode
        async for message in client.receive_response():
            if hasattr(message, "result"):
                print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  const q = query({
    prompt: "Help me refactor this code",
    options: {
      permissionMode: "default" // Start in default mode
    }
  });

  // Change mode dynamically mid-session
  await q.setPermissionMode("acceptEdits");

  // Process messages with the new permission mode
  for await (const message of q) {
    if ("result" in message) {
      console.log(message.result);
    }
  }
}

main();
```


[​](#mode-details)

Mode details


[​](#accept-edits-mode-acceptedits)

Accept edits mode (`acceptEdits`)

Auto-approves file operations so Claude can edit code without prompting. Other tools (like Bash commands that aren’t filesystem operations) still require normal permissions. **Auto-approved operations:**

- File edits (Edit, Write tools)
- Filesystem commands: `mkdir`, `touch`, `rm`, `rmdir`, `mv`, `cp`, `sed`

Both apply only to paths inside the working directory or `additionalDirectories`. In `acceptEdits` mode, Claude Code doesn’t auto-approve the request when Claude:

- Works on a path outside that scope
- Writes to a protected path
- Removes a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths) with `rm` or `rmdir`

**Use when:** you trust Claude’s edits and want faster iteration, such as during prototyping or when working in an isolated directory.


[​](#don’t-ask-mode-dontask)

Don’t ask mode (`dontAsk`)

Converts any permission prompt into a denial, without calling `canUseTool`. Tools pre-approved by `allowed_tools`, `settings.json` allow rules, or a hook run as normal, and so do calls that need no approval in `default` mode, such as file reads inside your working directories and calls to `Agent`. Connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools), tools that require user interaction, and `rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths) are denied even when an allow rule matches. A `PreToolUse` hook allow doesn’t clear a critical-path removal either. **Use when:** you want a fixed, explicit tool surface for a headless agent and prefer a hard deny over silent reliance on `canUseTool` being absent.


[​](#bypass-permissions-mode-bypasspermissions)

Bypass permissions mode (`bypassPermissions`)

Auto-approves tool uses without prompting, except the cases listed in the warning below. Hooks still execute and can block operations if needed. On Linux and macOS, Claude Code refuses to start in this mode as root or under `sudo` outside a [recognized sandbox](../02-Claude-Code-CLI/permission-modes.md#skip-all-checks-with-bypasspermissions-mode), and the query fails before the first turn.

Use with extreme caution. Claude has full system access in this mode. Only use in controlled environments where you trust all possible operations.`allowed_tools` does not constrain this mode. Every tool is approved, not just the ones you listed. These controls still apply:

- Deny rules, explicit `ask` rules, and hooks are evaluated before the mode check and can still block a tool.
- Connector tools [your organization set to `ask`](../06-MCP-Tools/General/mcp.md#organization-controls-on-connector-tools), tools that require user interaction, and `rm` and `rmdir` removals targeting a [critical path](../02-Claude-Code-CLI/permission-modes.md#critical-paths) still fall through to your `canUseTool` callback.
- The [cross-session messaging safeguards](../02-Claude-Code-CLI/permission-modes.md#skip-all-checks-with-bypasspermissions-mode) still apply.


[​](#plan-mode-plan)

Plan mode (`plan`)

Claude explores the codebase and produces a plan without editing your source files. Read-only tools run as they do in the `default` permission mode. File edits are never auto-approved in plan mode, even when an allow rule matches. They prompt through your `canUseTool` callback instead. On Claude Code v2.1.212 or later, shell commands that modify files, such as `touch` and `rm`, reach your `canUseTool` callback the same way. In the TypeScript SDK, if you set `allowDangerouslySkipPermissions: true` alongside `permissionMode: 'plan'`, file edits and shell commands that modify files still reach your `canUseTool` callback. The option lets you switch to `bypassPermissions` later with `setPermissionMode()`. Claude may use `AskUserQuestion` to clarify requirements before finalizing the plan. See [Handle approvals and user input](agent-sdk-user-input.md#handle-clarifying-questions) for handling these prompts. **Use when:** you want Claude to propose changes without executing them, such as during code review or when you need to approve changes before they’re made.


[​](#related-resources)

Related resources

For the other steps in the permission evaluation flow:

- [Handle approvals and user input](agent-sdk-user-input.md): interactive approval prompts and clarifying questions
- [Hooks guide](agent-sdk-hooks.md): run custom code at key points in the agent lifecycle
- [Permission rules](../02-Claude-Code-CLI/settings-reference.md#permission-settings): declarative allow/deny rules in `settings.json`

---

## Plugins in the SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/plugins
Path: 05-Agent-SDK/agent-sdk-plugins.md

## On this page

- [Loading plugins](#loading-plugins)
  - [Path specifications](#path-specifications)
- [Verifying plugin installation](#verifying-plugin-installation)
- [Use plugin skills](#use-plugin-skills)
- [Complete example](#complete-example)
- [Plugin structure reference](#plugin-structure-reference)
- [Multiple plugin sources](#multiple-plugin-sources)
- [Troubleshooting](#troubleshooting)
  - [Plugin not loading](#plugin-not-loading)
  - [Skills not appearing](#skills-not-appearing)
- [See also](#see-also)

Customize behavior

# Plugins in the SDK

Copy pageCopy page

Load custom plugins to extend Claude Code with skills, agents, hooks, and MCP servers through the Agent SDK

Copy pageCopy page

Plugins let you extend Claude Code with custom functionality that can be shared across projects. Through the Agent SDK, you can programmatically load plugins from local directories to add capabilities to your agent sessions. A plugin can include:

- **Skills**: capabilities Claude invokes autonomously when relevant. You can also invoke a plugin skill directly with `/plugin-name:skill-name`.
- **Agents**: specialized subagents for specific tasks
- **Hooks**: event handlers that respond to tool use and other events
- **MCP servers**: external tool integrations via Model Context Protocol

For complete information on plugin structure and how to create plugins, see [Plugins](../08-Plugins-Skills/plugins.md).


[​](#loading-plugins)

Loading plugins

Load plugins by providing their local file system paths in your options configuration. The `type` field must be `"local"`, the only value the SDK accepts. The SDK supports loading multiple plugins from different locations. To use a plugin distributed through a [marketplace](../08-Plugins-Skills/plugins.md) or remote repository, download it first and provide the local directory path. For the directory layout a plugin needs, see the [Plugin structure reference](#plugin-structure-reference) below.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Hello",
  options: {
    plugins: [
      { type: "local", path: "./my-plugin" },
      { type: "local", path: "/absolute/path/to/another-plugin" }
    ]
  }
})) {
  // Plugin commands, agents, and other features are now available
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Hello",
        options=ClaudeAgentOptions(
            plugins=[
                {"type": "local", "path": "./my-plugin"},
                {"type": "local", "path": "/absolute/path/to/another-plugin"},
            ]
        ),
    ):
        # Plugin commands, agents, and other features are now available
        pass


asyncio.run(main())
```


[​](#path-specifications)

Path specifications

Plugin paths can be:

- **Relative paths**: resolved relative to the `cwd` option (for example, `"./plugins/my-plugin"`)
- **Absolute paths**: full file system paths (for example, `"/home/user/plugins/my-plugin"`)

The path should point to the plugin’s root directory: the parent of `skills/`, `agents/`, `hooks/`, `commands/`, or `.claude-plugin/`.


[​](#verifying-plugin-installation)

Verifying plugin installation

When plugins load successfully, they appear in the system initialization message. You can verify that your plugins are available:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Hello",
  options: {
    plugins: [{ type: "local", path: "./my-plugin" }]
  }
})) {
  if (message.type === "system" && message.subtype === "init") {
    // Check loaded plugins
    console.log("Plugins:", message.plugins);
    // Example: [{ name: "my-plugin", path: "/absolute/path/to/my-plugin" }]

    // Plugin skills appear with the plugin name as a prefix
    console.log("Skills:", message.skills);
    // Example: ["my-plugin:greet"]

    // Plugin commands use the same prefix, and skills appear here too
    console.log("Commands:", message.slash_commands);
    // Example: ["compact", "context", "my-plugin:custom-command", "my-plugin:greet"]
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage


async def main():
    async for message in query(
        prompt="Hello",
        options=ClaudeAgentOptions(
            plugins=[{"type": "local", "path": "./my-plugin"}]
        ),
    ):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            # Check loaded plugins
            print("Plugins:", message.data.get("plugins"))
            # Example: [{"name": "my-plugin", "path": "/absolute/path/to/my-plugin"}]

            # Plugin skills appear with the plugin name as a prefix
            print("Skills:", message.data.get("skills"))
            # Example: ["my-plugin:greet"]

            # Plugin commands use the same prefix, and skills appear here too
            print("Commands:", message.data.get("slash_commands"))
            # Example: ["compact", "context", "my-plugin:custom-command", "my-plugin:greet"]


asyncio.run(main())
```


[​](#use-plugin-skills)

Use plugin skills

Skills from plugins are automatically namespaced with the plugin name to avoid conflicts. To invoke one directly, send `/plugin-name:skill-name` as the prompt.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Load a plugin with a custom /greet skill
for await (const message of query({
  prompt: "/my-plugin:greet", // Use plugin skill with namespace
  options: {
    plugins: [{ type: "local", path: "./my-plugin" }]
  }
})) {
  // Claude executes the custom greeting skill from the plugin
  if (message.type === "assistant") {
    console.log(message.message.content);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock


async def main():
    # Load a plugin with a custom /greet skill
    async for message in query(
        prompt="/my-plugin:greet",  # Use plugin skill with namespace
        options=ClaudeAgentOptions(
            plugins=[{"type": "local", "path": "./my-plugin"}]
        ),
    ):
        # Claude executes the custom greeting skill from the plugin
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(f"Claude: {block.text}")


asyncio.run(main())
```

If you installed a plugin via the CLI (for example, `/plugin install my-plugin@marketplace`), you can still use it in the SDK by providing its installation path. Check `~/.claude/plugins/` for CLI-installed plugins.


[​](#complete-example)

Complete example

Here’s a full example demonstrating plugin loading and usage:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";
import { fileURLToPath } from "node:url";

async function runWithPlugin() {
  const pluginPath = fileURLToPath(new URL("./plugins/my-plugin", import.meta.url));

  console.log("Loading plugin from:", pluginPath);

  for await (const message of query({
    prompt: "What custom commands do you have available?",
    options: {
      plugins: [{ type: "local", path: pluginPath }],
      maxTurns: 3
    }
  })) {
    if (message.type === "system" && message.subtype === "init") {
      console.log("Loaded plugins:", message.plugins);
      console.log("Available skills:", message.skills);
      console.log("Available commands:", message.slash_commands);
    }

    if (message.type === "assistant") {
      console.log("Assistant:", message.message.content);
    }
  }
}

runWithPlugin().catch(console.error);
```

```python
#!/usr/bin/env python3
"""Example demonstrating how to use plugins with the Agent SDK."""

import asyncio
from pathlib import Path

from claude_agent_sdk import (
    AssistantMessage,
    ClaudeAgentOptions,
    SystemMessage,
    TextBlock,
    query,
)


async def run_with_plugin():
    """Example using a custom plugin."""
    plugin_path = Path(__file__).parent / "plugins" / "my-plugin"

    print(f"Loading plugin from: {plugin_path}")

    options = ClaudeAgentOptions(
        plugins=[{"type": "local", "path": str(plugin_path)}],
        max_turns=3,
    )

    async for message in query(
        prompt="What custom commands do you have available?", options=options
    ):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            print(f"Loaded plugins: {message.data.get('plugins')}")
            print(f"Available skills: {message.data.get('skills')}")
            print(f"Available commands: {message.data.get('slash_commands')}")

        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(f"Assistant: {block.text}")


if __name__ == "__main__":
    asyncio.run(run_with_plugin())
```


[​](#plugin-structure-reference)

Plugin structure reference

A plugin directory typically contains a `.claude-plugin/plugin.json` manifest file. The manifest is optional. When omitted, Claude Code auto-discovers components from the directory layout. The directory can include:

```python
my-plugin/
├── .claude-plugin/
│   └── plugin.json          # Plugin manifest (optional, components auto-discovered without it)
├── skills/                   # Agent Skills (invoked autonomously or via /plugin-name:skill-name)
│   └── my-skill/
│       └── SKILL.md
├── commands/                 # Skills as flat .md files
│   └── custom-cmd.md
├── agents/                   # Custom agents
│   └── specialist.md
├── hooks/                    # Event handlers
│   └── hooks.json
└── .mcp.json                # MCP server definitions
```

The `commands/` directory holds skills as flat Markdown files. Use `skills/` for new plugins. Claude Code supports both locations.


[​](#multiple-plugin-sources)

Multiple plugin sources

Combine plugins from different locations:

```python
import * as os from "node:os";
import * as path from "node:path";

plugins: [
  { type: "local", path: "./local-plugin" },
  {
    type: "local",
    path: path.join(os.homedir(), ".claude", "custom-plugins", "shared-plugin")
  }
];
```

The SDK doesn’t expand tilde paths like `~/plugins`. If a plugin path doesn’t exist, the SDK skips that plugin and the session continues, so check the `plugins` list in the init message to confirm each plugin loaded.


[​](#troubleshooting)

Troubleshooting


[​](#plugin-not-loading)

Plugin not loading

If your plugin doesn’t appear in the init message’s `plugins` list, check its [`plugin_errors`](agent-sdk-typescript.md#sdksystemmessage) field for the reason, then work through these checks:

1.  **Check the path**: ensure the path points to the plugin root directory, the parent of `skills/`, `agents/`, `hooks/`, `commands/`, or `.claude-plugin/`
2.  **Validate plugin.json**: if your plugin includes a manifest, ensure it has valid JSON syntax
3.  **Check file permissions**: ensure the plugin directory is readable
4.  **Confirm the directory exists**: the SDK skips a nonexistent path, and the plugin doesn’t appear in the init message’s `plugins` list


[​](#skills-not-appearing)

Skills not appearing

If plugin skills don’t work:

1.  **Use the namespace**: invoke plugin skills as `/plugin-name:skill-name`
2.  **Check init message**: verify the skill appears in the `skills` list with the correct namespace
3.  **Validate skill files**: ensure each skill has a `SKILL.md` file in its own subdirectory under `skills/`, for example `skills/my-skill/SKILL.md`


[​](#see-also)

See also

- [Plugins](../08-Plugins-Skills/plugins.md) - Complete plugin development guide
- [Plugins reference](../08-Plugins-Skills/plugins-reference.md) - Technical specifications
- [Commands](agent-sdk-skills.md#dispatch-commands-by-name) - Dispatching commands in the SDK
- [Subagents](agent-sdk-subagents.md) - Working with specialized agents
- [Skills](agent-sdk-skills.md) - Using Agent Skills

---

## Agent SDK reference - Python - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/python
Path: 05-Agent-SDK/agent-sdk-python.md

## On this page

- [Installation](#installation)
- [Choosing between query() and ClaudeSDKClient](#choosing-between-query-and-claudesdkclient)
- [Functions](#functions)
  - [query()](#query)
  - [Parameters](#parameters)
  - [Returns](#returns)
  - [Example - With options](#example-with-options)
  - [tool()](#tool)
  - [Parameters](#parameters-2)
  - [Input schema options](#input-schema-options)
  - [Returns](#returns-2)
  - [Example](#example)
  - [ToolAnnotations](#toolannotations)
  - [create_sdk_mcp_server()](#create_sdk_mcp_server)
  - [Parameters](#parameters-3)
  - [Returns](#returns-3)
  - [Example](#example-2)
  - [list_sessions()](#list_sessions)
  - [Parameters](#parameters-4)
  - [Return type: SDKSessionInfo](#return-type-sdksessioninfo)
  - [Example](#example-3)
  - [get_session_messages()](#get_session_messages)
  - [Parameters](#parameters-5)
  - [Return type: SessionMessage](#return-type-sessionmessage)
  - [Example](#example-4)
  - [get_session_info()](#get_session_info)
  - [Parameters](#parameters-6)
  - [Example](#example-5)
  - [rename_session()](#rename_session)
  - [Parameters](#parameters-7)
  - [Example](#example-6)
  - [tag_session()](#tag_session)
  - [Parameters](#parameters-8)
  - [Example](#example-7)
- [Classes](#classes)
  - [ClaudeSDKClient](#claudesdkclient)
  - [Methods](#methods)
  - [Context Manager Support](#context-manager-support)
  - [Example - Continuing a conversation](#example-continuing-a-conversation)
  - [Example - Streaming input with ClaudeSDKClient](#example-streaming-input-with-claudesdkclient)
  - [Example - Using interrupts](#example-using-interrupts)
  - [Example - Advanced permission control](#example-advanced-permission-control)
- [Types](#types)
  - [SdkMcpTool](#sdkmcptool)
  - [Transport](#transport)
  - [ClaudeAgentOptions](#claudeagentoptions)
  - [Handle slow or stalled API responses](#handle-slow-or-stalled-api-responses)
  - [OutputFormat](#outputformat)
  - [SystemPromptPreset](#systempromptpreset)
  - [SystemPromptCustom](#systempromptcustom)
  - [SystemPromptFile](#systempromptfile)
  - [SettingSource](#settingsource)
  - [Default behavior](#default-behavior)
  - [Why use setting_sources](#why-use-setting_sources)
  - [Settings precedence](#settings-precedence)
  - [AgentDefinition](#agentdefinition)
  - [PermissionMode](#permissionmode)
  - [EffortLevel](#effortlevel)
  - [CanUseTool](#canusetool)
  - [ToolPermissionContext](#toolpermissioncontext)
  - [PermissionResult](#permissionresult)
  - [PermissionResultAllow](#permissionresultallow)
  - [PermissionResultDeny](#permissionresultdeny)
  - [PermissionUpdate](#permissionupdate)
  - [PermissionRuleValue](#permissionrulevalue)
  - [ToolsPreset](#toolspreset)
  - [ThinkingConfig](#thinkingconfig)
  - [TaskBudget](#taskbudget)
  - [SdkBeta](#sdkbeta)
  - [McpSdkServerConfig](#mcpsdkserverconfig)
  - [McpServerConfig](#mcpserverconfig)
  - [McpStdioServerConfig](#mcpstdioserverconfig)
  - [McpSSEServerConfig](#mcpsseserverconfig)
  - [McpHttpServerConfig](#mcphttpserverconfig)
  - [McpServerStatusConfig](#mcpserverstatusconfig)
  - [McpStatusResponse](#mcpstatusresponse)
  - [McpServerStatus](#mcpserverstatus)
  - [ContextUsageResponse](#contextusageresponse)
  - [SdkPluginConfig](#sdkpluginconfig)
- [Message Types](#message-types)
  - [Message](#message)
  - [UserMessage](#usermessage)
  - [AssistantMessage](#assistantmessage)
  - [AssistantMessageError](#assistantmessageerror)
  - [SystemMessage](#systemmessage)
  - [ResultMessage](#resultmessage)
  - [StreamEvent](#streamevent)
  - [RateLimitEvent](#ratelimitevent)
  - [RateLimitInfo](#ratelimitinfo)
  - [ConversationResetMessage](#conversationresetmessage)
  - [TaskStartedMessage](#taskstartedmessage)
  - [TaskUsage](#taskusage)
  - [TaskProgressMessage](#taskprogressmessage)
  - [TaskNotificationMessage](#tasknotificationmessage)
- [Content Block Types](#content-block-types)
  - [ContentBlock](#contentblock)
  - [TextBlock](#textblock)
  - [ThinkingBlock](#thinkingblock)
  - [ToolUseBlock](#tooluseblock)
  - [ToolResultBlock](#toolresultblock)
- [Error Types](#error-types)
  - [ClaudeSDKError](#claudesdkerror)
  - [CLINotFoundError](#clinotfounderror)
  - [CLIConnectionError](#cliconnectionerror)
  - [ProcessError](#processerror)
  - [ResultError](#resulterror)
  - [CLIJSONDecodeError](#clijsondecodeerror)
- [Hook Types](#hook-types)
  - [HookEvent](#hookevent)
  - [HookCallback](#hookcallback)
  - [HookContext](#hookcontext)
  - [HookMatcher](#hookmatcher)
  - [HookInput](#hookinput)
  - [BaseHookInput](#basehookinput)
  - [PreToolUseHookInput](#pretoolusehookinput)
  - [PostToolUseHookInput](#posttoolusehookinput)
  - [PostToolUseFailureHookInput](#posttoolusefailurehookinput)
  - [UserPromptSubmitHookInput](#userpromptsubmithookinput)
  - [StopHookInput](#stophookinput)
  - [SubagentStopHookInput](#subagentstophookinput)
  - [PreCompactHookInput](#precompacthookinput)
  - [NotificationHookInput](#notificationhookinput)
  - [SubagentStartHookInput](#subagentstarthookinput)
  - [PermissionRequestHookInput](#permissionrequesthookinput)
  - [HookJSONOutput](#hookjsonoutput)
  - [SyncHookJSONOutput](#synchookjsonoutput)
  - [HookSpecificOutput](#hookspecificoutput)
  - [AsyncHookJSONOutput](#asynchookjsonoutput)
  - [Hook Usage Example](#hook-usage-example)
- [Tool Input/Output Types](#tool-input%2Foutput-types)
  - [Agent](#agent)
  - [AskUserQuestion](#askuserquestion)
  - [Bash](#bash)
  - [Monitor](#monitor)
  - [Edit](#edit)
  - [Read](#read)
  - [Write](#write)
  - [Glob](#glob)
  - [Grep](#grep)
  - [NotebookEdit](#notebookedit)
  - [WebFetch](#webfetch)
  - [WebSearch](#websearch)
  - [TodoWrite](#todowrite)
  - [TaskCreate](#taskcreate)
  - [TaskUpdate](#taskupdate)
  - [TaskGet](#taskget)
  - [TaskList](#tasklist)
  - [TaskOutput](#taskoutput)
  - [TaskStop](#taskstop)
  - [ExitPlanMode](#exitplanmode)
  - [ListMcpResources](#listmcpresources)
  - [ReadMcpResource](#readmcpresource)
- [Build a continuous conversation interface](#build-a-continuous-conversation-interface)
- [Error handling](#error-handling)
- [Sandbox Configuration](#sandbox-configuration)
  - [SandboxSettings](#sandboxsettings)
  - [Example usage](#example-usage)
  - [SandboxNetworkConfig](#sandboxnetworkconfig)
  - [SandboxIgnoreViolations](#sandboxignoreviolations)
  - [Permissions Fallback for Unsandboxed Commands](#permissions-fallback-for-unsandboxed-commands)
- [See also](#see-also)

SDK references

# Agent SDK reference - Python

Copy pageCopy page

Complete API reference for the Python Agent SDK, including all functions, types, and classes.

Copy pageCopy page


[​](#installation)

Installation

Install the package into a virtual environment. On recent Debian, Ubuntu, and Homebrew Python installs, running `pip install` against system Python fails with `error: externally-managed-environment`.

```python
python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk
```

For uv, Windows PowerShell, and API key setup, see [Setup in the Agent SDK quickstart](agent-sdk-quickstart.md#setup).


[​](#choosing-between-query-and-claudesdkclient)

Choosing between `query()` and `ClaudeSDKClient`

The Python SDK provides two ways to interact with Claude Code:

| Feature             | `query()`                                      | `ClaudeSDKClient`                  |
|:--------------------|:-----------------------------------------------|:-----------------------------------|
| **Session**         | Creates a new session by default               | Reuses same session                |
| **Conversation**    | Single exchange                                | Multiple exchanges in same context |
| **Connection**      | Managed automatically                          | Manual control                     |
| **Streaming Input** | ✅ Supported                                   | ✅ Supported                       |
| **Interrupts**      | ❌ Not supported                               | ✅ Supported                       |
| **Hooks**           | ✅ Supported                                   | ✅ Supported                       |
| **Custom Tools**    | ✅ Supported                                   | ✅ Supported                       |
| **Continue Chat**   | Manual via `continue_conversation` or `resume` | ✅ Automatic                       |
| **Use Case**        | One-off tasks                                  | Continuous conversations           |

Use `ClaudeSDKClient` for interactive applications such as chat interfaces, or when the next action depends on Claude’s response.


[​](#functions)

Functions

Signature blocks and bare `async for` / `async with` fragments on this page are illustrative. To run them, wrap the body in `async def main(): ...` and call `asyncio.run(main())`.


[​](#query)

`query()`

Creates a new session for each interaction with Claude Code by default. Returns an async iterator that yields messages as they arrive. Each call to `query()` starts fresh with no memory of previous interactions unless you pass `continue_conversation=True` or `resume` in [`ClaudeAgentOptions`](#claudeagentoptions). See [Sessions](agent-sdk-sessions.md).

```python
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None,
    transport: Transport | None = None
) -> AsyncIterator[Message]
```


[​](#parameters)

Parameters

| Parameter   | Type                        | Description                                                                |
|:------------|:----------------------------|:---------------------------------------------------------------------------|
| `prompt`    | `str | AsyncIterable[dict]` | The input prompt as a string or async iterable for streaming mode          |
| `options`   | `ClaudeAgentOptions | None` | Optional configuration object (defaults to `ClaudeAgentOptions()` if None) |
| `transport` | `Transport | None`          | Optional custom transport for communicating with the CLI process           |


[​](#returns)

Returns

Returns an `AsyncIterator[Message]` that yields messages from the conversation.


[​](#example-with-options)

Example - With options

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are an expert Python developer",
        permission_mode="acceptEdits",
    )

    async for message in query(prompt="Create a Python web server", options=options):
        print(message)


asyncio.run(main())
```


[​](#tool)

`tool()`

Decorator for defining MCP tools with type safety.

```python
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any],
    annotations: ToolAnnotations | None = None
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]
```


[​](#parameters-2)

Parameters

| Parameter      | Type                                           | Description                                                                                    |
|:---------------|:-----------------------------------------------|:-----------------------------------------------------------------------------------------------|
| `name`         | `str`                                          | Unique identifier for the tool                                                                 |
| `description`  | `str`                                          | Human-readable description of what the tool does                                               |
| `input_schema` | `type | dict[str, Any]`                        | Schema defining the tool’s input parameters. See [Input schema options](#input-schema-options) |
| `annotations`  | [`ToolAnnotations`](#toolannotations)` | None` | Optional MCP tool annotations providing behavioral hints to clients                            |


[​](#input-schema-options)

Input schema options

1.  **Simple type mapping** (recommended):

    ``` shiki
    {"text": str, "count": int, "enabled": bool}
    ```
2.  **JSON Schema format** (for complex validation):

    ``` shiki
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0},
        },
        "required": ["text"],
    }
    ```
3.  **TypedDict class**: a typed schema whose `NotRequired` keys are left out of `required`.
    - **Python 3.11 and later**: import `TypedDict` and `NotRequired` from `typing`.
    - **Python 3.10**: `typing` has no `NotRequired`. Import `TypedDict` and `NotRequired` from `typing_extensions`, which the SDK installs on Python 3.10.

    ``` shiki
    from typing import Annotated, Any, NotRequired, TypedDict
    from claude_agent_sdk import tool


    class ForecastArgs(TypedDict):
        latitude: Annotated[float, "Latitude coordinate"]
        hours: NotRequired[Annotated[int, "How many hours of forecast to return"]]


    @tool("get_forecast", "Get the hourly forecast for a location", ForecastArgs)
    async def get_forecast(args: dict[str, Any]) -> dict[str, Any]:
        hours = args.get("hours", 12)
        return {"content": [{"type": "text", "text": f"{hours}-hour forecast for {args['latitude']}"}]}
    ```

In the simple mapping and TypedDict forms, wrap a type in `Annotated[type, "description"]` to set the field’s description.


[​](#returns-2)

Returns

A decorator function that wraps the tool implementation and returns an `SdkMcpTool` instance.


[​](#example)

Example

```python
from claude_agent_sdk import tool
from typing import Any


@tool("greet", "Greet a user", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
```


[​](#toolannotations)

`ToolAnnotations`

Behavioral hints for a tool, passed as the `annotations` argument of [`tool()`](#tool). `ToolAnnotations` extends the MCP SDK’s `mcp.types.ToolAnnotations` with a `maxResultSizeChars` field, and you can write each hint in camelCase or snake_case: `ToolAnnotations(readOnlyHint=True)` and `ToolAnnotations(read_only_hint=True)` are equivalent. To read a hint back from the object, use the spelling your installed `mcp` package declares: `.readOnlyHint` on `mcp` 1.x and `.read_only_hint` on 2.x, while `.maxResultSizeChars` works on both. You can also pass a plain `mcp.types.ToolAnnotations` wherever the SDK accepts annotations. The snake_case names and the typed `maxResultSizeChars` field require Python Agent SDK 0.2.140 or later. Versions 0.1.31 through 0.2.139 re-export `mcp.types.ToolAnnotations` unchanged. On versions 0.1.55 through 0.2.139 you can still pass `maxResultSizeChars` as a keyword argument: the MCP class accepts extra fields, and the SDK forwards the value to Claude Code. All fields are optional. Clients shouldn’t rely on the hints for security decisions.

| Field                | Type          | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                            |
|:---------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `title`              | `str | None`  | `None`  | Human-readable title for the tool                                                                                                                                                                                                                                                                                                                                                                                      |
| `readOnlyHint`       | `bool | None` | `False` | If `True`, the tool does not modify its environment                                                                                                                                                                                                                                                                                                                                                                    |
| `destructiveHint`    | `bool | None` | `True`  | If `True`, the tool may perform destructive updates (only meaningful when `readOnlyHint` is `False`)                                                                                                                                                                                                                                                                                                                   |
| `idempotentHint`     | `bool | None` | `False` | If `True`, repeated calls with the same arguments have no additional effect (only meaningful when `readOnlyHint` is `False`)                                                                                                                                                                                                                                                                                           |
| `openWorldHint`      | `bool | None` | `True`  | If `True`, the tool interacts with external entities (for example, web search). If `False`, the tool’s domain is closed (for example, a memory tool)                                                                                                                                                                                                                                                                   |
| `maxResultSizeChars` | `int | None`  | `None`  | Number of characters up to which Claude Code keeps this tool’s text result inline in the conversation instead of saving it to a file, up to 500,000. Results that contain images aren’t affected. A Claude Code setting rather than an MCP hint: the SDK sends it in the tool’s `_meta` as `anthropic/maxResultSizeChars`. See [Raise the limit for a specific tool](../06-MCP-Tools/General/mcp.md#raise-the-limit-for-a-specific-tool) |

```python
from claude_agent_sdk import tool, ToolAnnotations
from typing import Any


@tool(
    "search",
    "Search the web",
    {"query": str},
    annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
)
async def search(args: dict[str, Any]) -> dict[str, Any]:
    return {"content": [{"type": "text", "text": f"Results for: {args['query']}"}]}
```


[​](#create_sdk_mcp_server)

`create_sdk_mcp_server()`

Create an in-process MCP server that runs within your Python application.

```python
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig
```


[​](#parameters-3)

Parameters

| Parameter | Type                           | Default   | Description                                           |
|:----------|:-------------------------------|:----------|:------------------------------------------------------|
| `name`    | `str`                          | \-        | Unique identifier for the server                      |
| `version` | `str`                          | `"1.0.0"` | Server version string                                 |
| `tools`   | `list[SdkMcpTool[Any]] | None` | `None`    | List of tool functions created with `@tool` decorator |


[​](#returns-3)

Returns

Returns an `McpSdkServerConfig` object that can be passed to `ClaudeAgentOptions.mcp_servers`.


[​](#example-2)

Example

```python
from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions


@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
    return {"content": [{"type": "text", "text": f"Sum: {args['a'] + args['b']}"}]}


@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply(args):
    return {"content": [{"type": "text", "text": f"Product: {args['a'] * args['b']}"}]}


calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply],  # Pass decorated functions
)

# Use with Claude
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=["mcp__calc__add", "mcp__calc__multiply"],
)
```


[​](#list_sessions)

`list_sessions()`

Lists past sessions with metadata. Filter by project directory or list sessions across all projects. Synchronous; returns immediately.

```python
def list_sessions(
    directory: str | None = None,
    limit: int | None = None,
    offset: int = 0,
    include_worktrees: bool = True
) -> list[SDKSessionInfo]
```


[​](#parameters-4)

Parameters

| Parameter           | Type         | Default | Description                                                                                      |
|:--------------------|:-------------|:--------|:-------------------------------------------------------------------------------------------------|
| `directory`         | `str | None` | `None`  | Directory to list sessions for. When omitted, returns sessions across all projects               |
| `limit`             | `int | None` | `None`  | Maximum number of sessions to return                                                             |
| `offset`            | `int`        | `0`     | Number of sessions to skip from the start of the sorted results. Use with `limit` for pagination |
| `include_worktrees` | `bool`       | `True`  | When `directory` is inside a git repository, include sessions from all worktree paths            |


[​](#return-type-sdksessioninfo)

Return type: `SDKSessionInfo`

| Property        | Type         | Description                                                                              |
|:----------------|:-------------|:-----------------------------------------------------------------------------------------|
| `session_id`    | `str`        | Unique session identifier                                                                |
| `summary`       | `str`        | Display title: custom title, most recent prompt, auto-generated summary, or first prompt |
| `last_modified` | `int`        | Last modified time in milliseconds since epoch                                           |
| `file_size`     | `int | None` | Session file size in bytes (`None` for remote storage backends)                          |
| `custom_title`  | `str | None` | Session title: the user-set title, or the auto-generated title when none is set          |
| `first_prompt`  | `str | None` | First meaningful user prompt in the session                                              |
| `git_branch`    | `str | None` | Git branch at the end of the session                                                     |
| `cwd`           | `str | None` | Working directory for the session                                                        |
| `tag`           | `str | None` | User-set session tag (see [`tag_session()`](#tag_session))                               |
| `created_at`    | `int | None` | Session creation time in milliseconds since epoch                                        |


[​](#example-3)

Example

Print the 10 most recent sessions for a project. Results are sorted by `last_modified` descending, so the first item is the newest. Omit `directory` to search across all projects.

```python
from claude_agent_sdk import list_sessions

for session in list_sessions(directory="/path/to/project", limit=10):
    print(f"{session.summary} ({session.session_id})")
```


[​](#get_session_messages)

`get_session_messages()`

Retrieves messages from a past session. Synchronous; returns immediately.

```python
def get_session_messages(
    session_id: str,
    directory: str | None = None,
    limit: int | None = None,
    offset: int = 0
) -> list[SessionMessage]
```


[​](#parameters-5)

Parameters

| Parameter    | Type         | Default  | Description                                                       |
|:-------------|:-------------|:---------|:------------------------------------------------------------------|
| `session_id` | `str`        | required | The session ID to retrieve messages for                           |
| `directory`  | `str | None` | `None`   | Project directory to look in. When omitted, searches all projects |
| `limit`      | `int | None` | `None`   | Maximum number of messages to return                              |
| `offset`     | `int`        | `0`      | Number of messages to skip from the start                         |


[​](#return-type-sessionmessage)

Return type: `SessionMessage`

| Property             | Type                           | Description                                                                                                                                                                                                                                                            |
|:---------------------|:-------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type`               | `Literal["user", "assistant"]` | Message role                                                                                                                                                                                                                                                           |
| `uuid`               | `str`                          | Unique message identifier                                                                                                                                                                                                                                              |
| `session_id`         | `str`                          | Session identifier                                                                                                                                                                                                                                                     |
| `message`            | `Any`                          | Raw message content                                                                                                                                                                                                                                                    |
| `parent_tool_use_id` | `str | None`                   | For subagent messages, the id of the spawning `Agent` tool-use block. `None` for main-session messages and older sessions                                                                                                                                              |
| `parent_agent_id`    | `str | None`                   | For messages from a [nested subagent](../09-Agents-Patterns/sub-agents.md#let-subagents-spawn-their-own-subagents), the agent id of the parent subagent. `None` for main-session messages, top-level subagent messages, and older sessions. Requires Python Agent SDK 0.2.140 or later |


[​](#example-4)

Example

```python
from claude_agent_sdk import list_sessions, get_session_messages

sessions = list_sessions(limit=1)
if sessions:
    messages = get_session_messages(sessions[0].session_id)
    for msg in messages:
        print(f"[{msg.type}] {msg.uuid}")
```


[​](#get_session_info)

`get_session_info()`

Reads metadata for a single session by ID without scanning the full project directory. Synchronous; returns immediately.

```python
def get_session_info(
    session_id: str,
    directory: str | None = None,
) -> SDKSessionInfo | None
```


[​](#parameters-6)

Parameters

| Parameter    | Type         | Default  | Description                                                            |
|:-------------|:-------------|:---------|:-----------------------------------------------------------------------|
| `session_id` | `str`        | required | UUID of the session to look up                                         |
| `directory`  | `str | None` | `None`   | Project directory path. When omitted, searches all project directories |

Returns [`SDKSessionInfo`](#return-type-sdksessioninfo), or `None` if the session is not found.


[​](#example-5)

Example

Look up a single session’s metadata without scanning the project directory. Useful when you already have a session ID from a previous run.

```python
from claude_agent_sdk import get_session_info

info = get_session_info("550e8400-e29b-41d4-a716-446655440000")
if info:
    print(f"{info.summary} (branch: {info.git_branch}, tag: {info.tag})")
```


[​](#rename_session)

`rename_session()`

Renames a session by appending a custom-title entry. Repeated calls are safe; the most recent title wins. Synchronous.

```python
def rename_session(
    session_id: str,
    title: str,
    directory: str | None = None,
) -> None
```


[​](#parameters-7)

Parameters

| Parameter    | Type         | Default  | Description                                                            |
|:-------------|:-------------|:---------|:-----------------------------------------------------------------------|
| `session_id` | `str`        | required | UUID of the session to rename                                          |
| `title`      | `str`        | required | New title. Must be non-empty after stripping whitespace                |
| `directory`  | `str | None` | `None`   | Project directory path. When omitted, searches all project directories |

Raises `ValueError` if `session_id` is not a valid UUID or `title` is empty; `FileNotFoundError` if the session cannot be found.


[​](#example-6)

Example

Rename the most recent session so it’s easier to find later. The new title appears in [`SDKSessionInfo.custom_title`](#return-type-sdksessioninfo) on subsequent reads.

```python
from claude_agent_sdk import list_sessions, rename_session

sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
    rename_session(sessions[0].session_id, "Refactor auth module")
```


[​](#tag_session)

`tag_session()`

Tags a session. Pass `None` to clear the tag. Repeated calls are safe; the most recent tag wins. Synchronous.

```python
def tag_session(
    session_id: str,
    tag: str | None,
    directory: str | None = None,
) -> None
```


[​](#parameters-8)

Parameters

| Parameter    | Type         | Default  | Description                                                            |
|:-------------|:-------------|:---------|:-----------------------------------------------------------------------|
| `session_id` | `str`        | required | UUID of the session to tag                                             |
| `tag`        | `str | None` | required | Tag string, or `None` to clear. Unicode-sanitized before storing       |
| `directory`  | `str | None` | `None`   | Project directory path. When omitted, searches all project directories |

Raises `ValueError` if `session_id` is not a valid UUID or `tag` is empty after sanitization; `FileNotFoundError` if the session cannot be found.


[​](#example-7)

Example

Tag a session, then filter by that tag on a later read. Pass `None` to clear an existing tag.

```python
from claude_agent_sdk import list_sessions, tag_session

# Tag the most recent session
sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
    tag_session(sessions[0].session_id, "needs-review")

# Later: find all sessions with that tag
for session in list_sessions(directory="/path/to/project"):
    if session.tag == "needs-review":
        print(session.summary)
```


[​](#classes)

Classes


[​](#claudesdkclient)

`ClaudeSDKClient`

**Maintains a conversation session across multiple exchanges.** This is the Python equivalent of how the TypeScript SDK’s `query()` function works internally - it creates a client object that can continue conversations. See the [comparison with `query()`](#choosing-between-query-and-claudesdkclient).

```python
class ClaudeSDKClient:
    def __init__(self, options: ClaudeAgentOptions | None = None, transport: Transport | None = None)
    async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None
    async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None
    async def receive_messages(self) -> AsyncIterator[Message]
    async def receive_response(self) -> AsyncIterator[Message]
    async def interrupt(self) -> None
    async def set_permission_mode(self, mode: PermissionMode) -> None
    async def set_model(self, model: str | None = None) -> None
    async def rewind_files(self, user_message_id: str) -> None
    async def get_mcp_status(self) -> McpStatusResponse
    async def get_context_usage(self) -> ContextUsageResponse
    async def reconnect_mcp_server(self, server_name: str) -> None
    async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None
    async def stop_task(self, task_id: str) -> None
    async def get_server_info(self) -> dict[str, Any] | None
    async def disconnect(self) -> None
```


[​](#methods)

Methods

| Method                                    | Description                                                                                                                                                                                                                                                                                                                                                                   |
|:------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `__init__(options)`                       | Initialize the client with optional configuration                                                                                                                                                                                                                                                                                                                             |
| `connect(prompt)`                         | Connect to Claude with an optional initial prompt or message stream                                                                                                                                                                                                                                                                                                           |
| `query(prompt, session_id)`               | Send a new request in streaming mode                                                                                                                                                                                                                                                                                                                                          |
| `receive_messages()`                      | Receive all messages from Claude as an async iterator                                                                                                                                                                                                                                                                                                                         |
| `receive_response()`                      | Receive messages until and including a ResultMessage                                                                                                                                                                                                                                                                                                                          |
| `interrupt()`                             | Send interrupt signal (only works in streaming mode)                                                                                                                                                                                                                                                                                                                          |
| `set_permission_mode(mode)`               | Change the permission mode for the current session                                                                                                                                                                                                                                                                                                                            |
| `set_model(model)`                        | Change the model for the current session. Pass `None` to reset to [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md)                                                                                                                                                                                                                                                        |
| `rewind_files(user_message_id)`           | Restore files to their state at the specified user message. Requires `enable_file_checkpointing=True`. See [File checkpointing](agent-sdk-file-checkpointing.md)                                                                                                                                                                                                        |
| `get_mcp_status()`                        | Get the status of all configured MCP servers. Returns [`McpStatusResponse`](#mcpstatusresponse)                                                                                                                                                                                                                                                                               |
| `get_context_usage()`                     | Get a breakdown of context window usage by category, skill, and tool. The same data `/context` shows in an interactive session. Returns [`ContextUsageResponse`](#contextusageresponse). To compute the breakdown, Claude Code makes several token-counting API requests that don’t appear in the message stream; see [how these requests are handled](#contextusageresponse) |
| `reconnect_mcp_server(server_name)`       | Retry connecting to an MCP server that failed or was disconnected                                                                                                                                                                                                                                                                                                             |
| `toggle_mcp_server(server_name, enabled)` | Enable or disable an MCP server mid-session. Disabling a stdio, SSE, or HTTP server removes its tools                                                                                                                                                                                                                                                                         |
| `stop_task(task_id)`                      | Stop a running background task. A [`TaskNotificationMessage`](#tasknotificationmessage) with status `"stopped"` follows in the message stream                                                                                                                                                                                                                                 |
| `get_server_info()`                       | Get the server’s initialization info, including available commands and output styles                                                                                                                                                                                                                                                                                          |
| `disconnect()`                            | Disconnect from Claude                                                                                                                                                                                                                                                                                                                                                        |


[​](#context-manager-support)

Context Manager Support

The client can be used as an async context manager for automatic connection management:

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient


async def main():
    async with ClaudeSDKClient() as client:
        await client.query("Hello Claude")
        async for message in client.receive_response():
            print(message)


asyncio.run(main())
```

> **Important:** When iterating over messages, avoid using `break` to exit early as this can cause asyncio cleanup issues. Instead, let the iteration complete naturally or use flags to track when you’ve found what you need.


[​](#example-continuing-a-conversation)

Example - Continuing a conversation

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage


async def main():
    async with ClaudeSDKClient() as client:
        # First question
        await client.query("What's the capital of France?")

        # Process response
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # Follow-up question - the session retains the previous context
        await client.query("What's the population of that city?")

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # Another follow-up - still in the same conversation
        await client.query("What are some famous landmarks there?")

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")


asyncio.run(main())
```


[​](#example-streaming-input-with-claudesdkclient)

Example - Streaming input with ClaudeSDKClient

`query()` also accepts an async iterable of user message dicts, so you can assemble the prompt at send time or include content blocks such as images. Claude Code starts responding to the first yielded message as soon as it arrives, without waiting for the iterable to finish, and `receive_response()` stops at the `ResultMessage` that ends that response. Put everything Claude should read before answering into one message, as this generator does, and pair each `query()` call with its own `receive_response()` loop.

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient


async def message_stream():
    """Assemble the prompt at send time and yield it as one user message."""
    readings = {"Temperature": "25°C", "Humidity": "60%"}
    data = ", ".join(f"{name}: {value}" for name, value in readings.items())
    yield {
        "type": "user",
        "message": {
            "role": "user",
            "content": f"Analyze the following sensor data and describe any patterns you see: {data}",
        },
    }


async def main():
    async with ClaudeSDKClient() as client:
        # Stream input to Claude
        await client.query(message_stream())

        # Process response
        async for message in client.receive_response():
            print(message)

        # Follow-up in same session
        await client.query("Should we be concerned about these readings?")

        async for message in client.receive_response():
            print(message)


asyncio.run(main())
```


[​](#example-using-interrupts)

Example - Using interrupts

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, ResultMessage


async def interruptible_task():
    options = ClaudeAgentOptions(allowed_tools=["Bash"], permission_mode="acceptEdits")

    async with ClaudeSDKClient(options=options) as client:
        # Start a long-running task
        await client.query("Count from 1 to 100 slowly, using the bash sleep command")

        # Let it run for a bit
        await asyncio.sleep(2)

        # Interrupt the task
        await client.interrupt()
        print("Task interrupted!")

        # Drain the interrupted task's messages (including its ResultMessage)
        async for message in client.receive_response():
            if isinstance(message, ResultMessage):
                print(f"Interrupted task: terminal_reason={message.terminal_reason!r}")
                # terminal_reason is "aborted_streaming" or "aborted_tools"
                # for interrupted turns

        # Send a new command
        await client.query("Just say hello instead")

        # Now receive the new response
        async for message in client.receive_response():
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(f"New result: {message.result}")


asyncio.run(interruptible_task())
```

**Buffer behavior after interrupt:** `interrupt()` sends a stop signal but does not clear the message buffer. Messages already produced by the interrupted task, including its `ResultMessage`, remain in the stream. You must drain them with `receive_response()` before reading the response to a new query. If you send a new query immediately after `interrupt()` and call `receive_response()` only once, you’ll receive the interrupted task’s messages, not the new query’s response.


[​](#example-advanced-permission-control)

Example - Advanced permission control

```python
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
from claude_agent_sdk.types import (
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


async def custom_permission_handler(
    tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Custom logic for tool permissions."""

    # Block writes to system directories
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return PermissionResultDeny(
            message="System directory write not allowed", interrupt=True
        )

    # Redirect sensitive file operations
    if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""):
        safe_path = f"./sandbox/{input_data['file_path']}"
        return PermissionResultAllow(
            updated_input={**input_data, "file_path": safe_path}
        )

    # Allow everything else
    return PermissionResultAllow(updated_input=input_data)


async def main():
    # Don't also list the gated tools in allowed_tools: allow rules approve calls before can_use_tool runs
    options = ClaudeAgentOptions(can_use_tool=custom_permission_handler)

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Update the system config file")

        async for message in client.receive_response():
            # Will use sandbox path instead
            print(message)


asyncio.run(main())
```


[​](#types)

Types

**`@dataclass` vs `TypedDict`:** This SDK uses two kinds of types. Classes decorated with `@dataclass` (such as `ResultMessage`, `AgentDefinition`, `TextBlock`) are object instances at runtime and support attribute access: `msg.result`. Classes defined with `TypedDict` (such as `ThinkingConfigEnabled`, `McpStdioServerConfig`, `SyncHookJSONOutput`) are **plain dicts at runtime** and require key access: `config["budget_tokens"]`, not `config.budget_tokens`. The `ClassName(field=value)` call syntax works for both, but only dataclasses produce objects with attributes.


[​](#sdkmcptool)

`SdkMcpTool`

Definition for an SDK MCP tool created with the `@tool` decorator.

```python
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
    annotations: ToolAnnotations | None = None
```

| Property       | Type                                           | Description                                                                                                      |
|:---------------|:-----------------------------------------------|:-----------------------------------------------------------------------------------------------------------------|
| `name`         | `str`                                          | Unique identifier for the tool                                                                                   |
| `description`  | `str`                                          | Human-readable description                                                                                       |
| `input_schema` | `type[T] | dict[str, Any]`                     | Schema for input validation                                                                                      |
| `handler`      | `Callable[[T], Awaitable[dict[str, Any]]]`     | Async function that handles tool execution                                                                       |
| `annotations`  | [`ToolAnnotations`](#toolannotations)` | None` | Optional tool annotations (for example `readOnlyHint`, `destructiveHint`, `openWorldHint`, `maxResultSizeChars`) |


[​](#transport)

`Transport`

Abstract base class for custom transport implementations. Use this to communicate with the Claude process over a custom channel (for example, a remote connection instead of a local subprocess).

This is a low-level internal API. The interface may change in future releases. Custom implementations must be updated to match any interface changes.

```python
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator
from typing import Any


class Transport(ABC):
    @abstractmethod
    async def connect(self) -> None: ...

    @abstractmethod
    async def write(self, data: str) -> None: ...

    @abstractmethod
    def read_messages(self) -> AsyncIterator[dict[str, Any]]: ...

    @abstractmethod
    async def close(self) -> None: ...

    @abstractmethod
    def is_ready(self) -> bool: ...

    @abstractmethod
    async def end_input(self) -> None: ...
```

| Method            | Description                                                                 |
|:------------------|:----------------------------------------------------------------------------|
| `connect()`       | Connect the transport and prepare for communication                         |
| `write(data)`     | Write raw data (JSON + newline) to the transport                            |
| `read_messages()` | Async iterator that yields parsed JSON messages                             |
| `close()`         | Close the connection and clean up resources                                 |
| `is_ready()`      | Returns `True` if the transport can send and receive                        |
| `end_input()`     | Close the input stream (for example, close stdin for subprocess transports) |

Import: `from claude_agent_sdk import Transport`


[​](#claudeagentoptions)

`ClaudeAgentOptions`

Configuration dataclass for Claude Code queries.

```python
@dataclass
class ClaudeAgentOptions:
    tools: list[str] | ToolsPreset | None = None
    allowed_tools: list[str] = field(default_factory=list)
    system_prompt: str | SystemPromptPreset | SystemPromptCustom | SystemPromptFile | None = None
    mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
    strict_mcp_config: bool = False
    permission_mode: PermissionMode | None = None
    continue_conversation: bool = False
    resume: str | None = None
    session_id: str | None = None
    max_turns: int | None = None
    max_budget_usd: float | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | None = None
    fallback_model: str | None = None
    betas: list[SdkBeta] = field(default_factory=list)
    output_format: dict[str, Any] | None = None
    permission_prompt_tool_name: str | None = None
    cwd: str | Path | None = None
    cli_path: str | Path | None = None
    settings: str | None = None
    add_dirs: list[str | Path] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    extra_args: dict[str, str | None] = field(default_factory=dict)
    max_buffer_size: int | None = None
    debug_stderr: Any = sys.stderr  # Deprecated
    stderr: Callable[[str], None] | None = None
    can_use_tool: CanUseTool | None = None
    hooks: dict[HookEvent, list[HookMatcher]] | None = None
    user: str | None = None
    include_partial_messages: bool = False
    include_hook_events: bool = False
    forward_subagent_text: bool = False
    verbatim_prompts: bool = False
    fork_session: bool = False
    resume_session_at: str | None = None
    resume_drops_turn: str | None = None
    agents: dict[str, AgentDefinition] | None = None
    setting_sources: list[SettingSource] | None = None
    skills: list[str] | Literal["all"] | None = None
    sandbox: SandboxSettings | None = None
    plugins: list[SdkPluginConfig] = field(default_factory=list)
    max_thinking_tokens: int | None = None  # Deprecated: use thinking instead
    thinking: ThinkingConfig | None = None
    effort: EffortLevel | None = None
    enable_file_checkpointing: bool = False
    session_store: SessionStore | None = None
    session_store_flush: SessionStoreFlushMode = "batched"
    load_timeout_ms: int = 60_000
    task_budget: TaskBudget | None = None
```

| Property                      | Type                                                                                      | Default                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|:------------------------------|:------------------------------------------------------------------------------------------|:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `tools`                       | `list[str] | ToolsPreset | None`                                                          | `None`                             | Tools configuration. Use `{"type": "preset", "preset": "claude_code"}` for Claude Code’s default tools                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `allowed_tools`               | `list[str]`                                                                               | `[]`                               | Tools to auto-approve without prompting. This does not restrict Claude to only these tools. If you name one of the [task-tracking tools](agent-sdk-todo-tracking.md#model-availability) here, Claude Code also opts the session in. Other unlisted tools fall through to `permission_mode` and `can_use_tool`. Use `disallowed_tools` to block tools. See [Permissions](agent-sdk-permissions.md#allow-and-deny-rules)                                                                                                                                                                                                                                    |
| `system_prompt`               | `str | SystemPromptPreset | SystemPromptCustom | SystemPromptFile | None`                 | `None`                             | System prompt configuration. Pass a string for a custom prompt, `{"type": "preset", "preset": "claude_code"}` for Claude Code’s system prompt with optional `"append"`, `{"type": "custom", "prompt": "..."}` for a custom prompt that can also set `"snapshot"`, or `{"type": "file", "path": "..."}` to load a large prompt from disk. See [`SystemPromptPreset`](#systempromptpreset), [`SystemPromptCustom`](#systempromptcustom), and [`SystemPromptFile`](#systempromptfile)                                                                                                                                                                                    |
| `mcp_servers`                 | `dict[str, McpServerConfig] | str | Path`                                                 | `{}`                               | MCP server configurations or path to config file                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `strict_mcp_config`           | `bool`                                                                                    | `False`                            | When `True`, use only the servers passed in `mcp_servers` and ignore project `.mcp.json`, user settings, plugin-provided MCP servers, and [claude.ai connectors](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai). Maps to the CLI `--strict-mcp-config` flag                                                                                                                                                                                                                                                                                                                                                                                                             |
| `permission_mode`             | `PermissionMode | None`                                                                   | `None`                             | Permission mode for tool usage                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `continue_conversation`       | `bool`                                                                                    | `False`                            | Continue the most recent conversation                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `resume`                      | `str | None`                                                                              | `None`                             | Session ID to resume                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `session_id`                  | `str | None`                                                                              | `None`                             | Use a specific session ID instead of an auto-generated one. Must be a valid UUID. Can’t be combined with `continue_conversation` or `resume` unless `fork_session` is also set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `max_turns`                   | `int | None`                                                                              | `None`                             | Maximum agentic turns (tool-use round trips)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `max_budget_usd`              | `float | None`                                                                            | `None`                             | Stop the query when the client-side cost estimate reaches this USD value. The estimate can pass this value, so [leave headroom](agent-sdk-agent-loop.md#budget-headroom). Counts only the call’s own spend; totals restored from a resumed session don’t count. For accuracy caveats and reset behavior, see [Track cost and usage](agent-sdk-cost-tracking.md)                                                                                                                                                                                                                                                                                           |
| `disallowed_tools`            | `list[str]`                                                                               | `[]`                               | Tools to deny. A bare name such as `"Bash"` removes the tool from Claude’s context. A scoped rule such as `"Bash(rm *)"` leaves the tool available and denies matching calls in every permission mode, including `bypassPermissions`, for the command [as written](../02-Claude-Code-CLI/permissions.md#bash-rule-limits). See [Permissions](agent-sdk-permissions.md#allow-and-deny-rules)                                                                                                                                                                                                                                                                                     |
| `enable_file_checkpointing`   | `bool`                                                                                    | `False`                            | Enable file change tracking for rewinding. See [File checkpointing](agent-sdk-file-checkpointing.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `model`                       | `str | None`                                                                              | `None`                             | Claude model alias or full model name. See [accepted values and provider-specific IDs](../02-Claude-Code-CLI/model-config.md#available-models)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `fallback_model`              | `str | None`                                                                              | `None`                             | Fallback model to use if the primary model fails. Accepts a comma-separated list. For guidance, see [Choose a model](agent-sdk-configuration.md#choose-a-model)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `betas`                       | `list[SdkBeta]`                                                                           | `[]`                               | Beta features to enable. See [`SdkBeta`](#sdkbeta) for available options                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `output_format`               | `dict[str, Any] | None`                                                                   | `None`                             | Output format for structured responses (e.g., `{"type": "json_schema", "schema": {...}}`). See [Structured outputs](agent-sdk-structured-outputs.md) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `permission_prompt_tool_name` | `str | None`                                                                              | `None`                             | MCP tool name for permission prompts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `cwd`                         | `str | Path | None`                                                                       | `None`                             | Current working directory                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `cli_path`                    | `str | Path | None`                                                                       | `None`                             | Custom path to the Claude Code CLI executable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `settings`                    | `str | None`                                                                              | `None`                             | Path to a settings file or an inline JSON string                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `add_dirs`                    | `list[str | Path]`                                                                        | `[]`                               | Additional directories Claude can access. The SDK passes each entry to Claude Code as `--add-dir`, so with the `project` setting source Claude Code also [loads the directory’s skills, commands, and subagents](../02-Claude-Code-CLI/permissions.md#additional-directories-grant-file-access-not-configuration)                                                                                                                                                                                                                                                                                                                                                                     |
| `env`                         | `dict[str, str]`                                                                          | `{}`                               | Environment variables merged on top of the inherited process environment. See [Environment variables](../02-Claude-Code-CLI/env-vars.md) for variables the underlying CLI reads, and [Handle slow or stalled API responses](#handle-slow-or-stalled-api-responses) for timeout-related variables. Set `CLAUDE_AGENT_SDK_CLIENT_APP` to identify your app in the User-Agent header                                                                                                                                                                                                                                                                                                     |
| `extra_args`                  | `dict[str, str | None]`                                                                   | `{}`                               | Additional CLI arguments to pass directly to the CLI                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `max_buffer_size`             | `int | None`                                                                              | `None`                             | Maximum bytes when buffering CLI stdout                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `debug_stderr`                | `Any`                                                                                     | `sys.stderr`                       | *Deprecated* - The SDK ignores this value. Use the `stderr` callback for CLI stderr output                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `stderr`                      | `Callable[[str], None] | None`                                                            | `None`                             | Callback function for stderr output from CLI                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `can_use_tool`                | [`CanUseTool`](#canusetool) ` | None`                                                     | `None`                             | Tool permission callback, invoked only when the [permission flow](agent-sdk-permissions.md#how-permissions-are-evaluated) falls through to a prompt. Not invoked for calls auto-approved by `allowed_tools`, allow rules, or `permission_mode`. An allow rule doesn’t pre-approve the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves). See [`CanUseTool`](#canusetool) for details                                                                                                                                                                                                                                     |
| `hooks`                       | `dict[HookEvent, list[HookMatcher]] | None`                                               | `None`                             | Hook configurations for intercepting events                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `user`                        | `str | None`                                                                              | `None`                             | On POSIX platforms, the OS user account the Claude Code subprocess runs as. Claude Code keeps the parent process’s environment, including `HOME`, and runs in `cwd`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `include_partial_messages`    | `bool`                                                                                    | `False`                            | Include partial message streaming events. When enabled, [`StreamEvent`](#streamevent) messages are yielded                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `include_hook_events`         | `bool`                                                                                    | `False`                            | Include hook lifecycle events in the message stream as `HookEventMessage` objects                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `forward_subagent_text`       | `bool`                                                                                    | `False`                            | Forward subagent text and thinking blocks in the message stream. Without this option, Claude Code omits the text and thinking blocks of a subagent that runs in the [foreground](../09-Agents-Patterns/sub-agents.md#run-subagents-in-foreground-or-background). For nested subagents, skills with `context: fork`, and the Claude Code version each needs, see [Follow subagent messages](../02-Claude-Code-CLI/headless.md#follow-subagent-messages). Requires Python Agent SDK 0.2.140 or later                                                                                                                                                                                                    |
| `verbatim_prompts`            | `bool`                                                                                    | `False`                            | Deliver every prompt as written. The SDK sends each user message with `client_composed` set to `True`. See [`client_composed`](agent-sdk-typescript.md#sdkusermessage) for what Claude Code skips on those messages. Use this option when your prompt text includes content the end user didn’t type. For per-turn control, leave it off and set `"client_composed": True` on individual streamed messages instead. While the option is on, the SDK overwrites any `client_composed` value you set. Requires Python Agent SDK 0.2.158 or later and Claude Code v2.1.248 or later; the CLI bundled with those SDK versions satisfies the Claude Code requirement |
| `fork_session`                | `bool`                                                                                    | `False`                            | When resuming with `resume`, fork to a new session ID instead of continuing the original session                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `resume_session_at`           | `str | None`                                                                              | `None`                             | When resuming, load the conversation only up to and including the message with this UUID. Use with `resume`, and usually `fork_session`, to branch from an earlier point. Requires Python Agent SDK 0.2.137 or later                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `resume_drops_turn`           | `str | None`                                                                              | `None`                             | UUID of the user prompt whose turn a `resume_session_at` truncation discards. When set, the CLI refuses the resume if the discarded range holds entries not attributable to that turn. Requires Python Agent SDK 0.2.137 or later and Claude Code v2.1.223 or later; the CLI bundled with those SDK versions satisfies the Claude Code requirement                                                                                                                                                                                                                                                                                                                    |
| `agents`                      | `dict[str, AgentDefinition] | None`                                                       | `None`                             | Programmatically defined subagents                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `plugins`                     | `list[SdkPluginConfig]`                                                                   | `[]`                               | Load custom plugins from local paths. See [Plugins](agent-sdk-plugins.md) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `sandbox`                     | [`SandboxSettings`](#sandboxsettings) ` | None`                                           | `None`                             | Configure sandbox behavior programmatically. See [Sandbox settings](#sandboxsettings) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `setting_sources`             | `list[SettingSource] | None`                                                              | `None` (CLI defaults: all sources) | Control which filesystem settings to load. Pass `[]` to disable user, project, and local settings. With `skills` set and this field unset, only user and project sources load. Set `setting_sources` explicitly to keep local settings. Endpoint-managed policy loads regardless; server-managed settings are fetched when the session authenticates with an organization credential on an [eligible configuration](../13-Enterprise-Admin/server-managed-settings.md#platform-availability). For inputs read regardless of this option, see [What settingSources does not control](agent-sdk-claude-code-features.md#what-settingsources-does-not-control)                      |
| `skills`                      | `list[str] | Literal["all"] | None`                                                       | `None`                             | Skills available to the session. Pass `"all"` to enable every discovered skill, or a list of skill names. Pass exact names only. The SDK rejects malformed and wildcard-form names with a `ValueError` before starting the Claude Code process; this check requires Python Agent SDK 0.2.129 or later. When set, the SDK adds the Skill tool to `allowed_tools` automatically. If you also pass `tools`, include `"Skill"` in that list. See [Skills](agent-sdk-skills.md)                                                                                                                                                                                      |
| `max_thinking_tokens`         | `int | None`                                                                              | `None`                             | *Deprecated* - Maximum tokens for thinking blocks. Use `thinking` instead                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `thinking`                    | [`ThinkingConfig`](#thinkingconfig) ` | None`                                             | `None`                             | Controls extended thinking behavior. Takes precedence over `max_thinking_tokens`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `effort`                      | [`EffortLevel`](#effortlevel) ` | None`                                                   | `None`                             | Effort level for thinking depth. See [adjust the effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `session_store`               | [`SessionStore`](agent-sdk-session-storage.md#the-sessionstore-interface) ` | None` | `None`                             | Mirror session transcripts to an external backend so another host can resume them. See [Persist sessions to external storage](agent-sdk-session-storage.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `session_store_flush`         | `Literal["batched", "eager"]`                                                             | `"batched"`                        | When to flush mirrored transcript entries to `session_store`. `"batched"` flushes once per turn or when the buffer fills; `"eager"` triggers a background flush after every frame. Ignored when `session_store` is `None`                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `load_timeout_ms`             | `int`                                                                                     | `60000`                            | Per-call timeout for `session_store.load()` and `list_subkeys()` during resume materialization, in milliseconds                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `task_budget`                 | `TaskBudget | None`                                                                       | `None`                             | API-side token budget. Sent as `output_config.task_budget` with the `task-budgets-2026-03-13` beta header. Pass `{"total": <int>}`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |


[​](#handle-slow-or-stalled-api-responses)

Handle slow or stalled API responses

The CLI subprocess reads several environment variables that control API timeouts and stall detection. Pass them through `ClaudeAgentOptions.env`:

```python
from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(
    env={
        "API_TIMEOUT_MS": "120000",
        "CLAUDE_CODE_MAX_RETRIES": "2",
        "CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS": "120000",
    },
)
```

- `API_TIMEOUT_MS`: per-request timeout on the Anthropic client, in milliseconds. Default `600000`. Applies to the main loop and all subagents.
- `CLAUDE_CODE_MAX_RETRIES`: maximum API retries. Default `10`, capped at `15`. Each retry gets its own `API_TIMEOUT_MS` window. For unattended runs that need to wait through longer outages, set [`CLAUDE_CODE_RETRY_WATCHDOG=1`](../02-Claude-Code-CLI/errors.md#tune-retry-behavior): it retries transient capacity errors indefinitely and, on Claude Code v2.1.199 or later, raises the default for other transient errors to `300` and removes the cap on this variable.
- `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS`: stall watchdog for subagents. While the stream watchdog is on, the default is `CLAUDE_STREAM_IDLE_TIMEOUT_MS` plus 5 minutes, which comes to `600000` unless you raise that variable. With the stream watchdog off, the default is `600000`. Before v2.1.257, the default was always `600000`. The timer resets on each stream event. On a stall, Claude Code aborts the subagent and reports the stall to the parent. For a background subagent, it also marks the task failed and attaches any partial result.
- `CLAUDE_ENABLE_STREAM_WATCHDOG` with `CLAUDE_STREAM_IDLE_TIMEOUT_MS`: stream watchdog that aborts the request when headers have arrived but the response body stops streaming. The watchdog is on by default for all providers; set `CLAUDE_ENABLE_STREAM_WATCHDOG=0` to disable it. `CLAUDE_STREAM_IDLE_TIMEOUT_MS` defaults to `300000` and is clamped to that minimum. After the abort, [Automatic retries](../02-Claude-Code-CLI/errors.md#automatic-retries) covers what Claude Code does, based on how far the response had progressed. While the watchdog waits out a response that a gateway behind `ANTHROPIC_BASE_URL` holds open with keep-alive pings, a host that sets `include_partial_messages` keeps receiving `ping` [`StreamEvent`](#streamevent) messages. Read those frames as liveness rather than timing the session out on silence. Before v2.1.257, the frames stopped 5 minutes after the last real stream event.


[​](#outputformat)

`OutputFormat`

Configuration for structured output validation. Pass this as a `dict` to the `output_format` field on `ClaudeAgentOptions`:

```python
# Expected dict shape for output_format
{
    "type": "json_schema",
    "schema": {...},  # Your JSON Schema definition
}
```

| Field    | Required | Description                                        |
|:---------|:---------|:---------------------------------------------------|
| `type`   | Yes      | Must be `"json_schema"` for JSON Schema validation |
| `schema` | Yes      | JSON Schema definition for output validation       |


[​](#systempromptpreset)

`SystemPromptPreset`

Configuration for using Claude Code’s preset system prompt with optional additions.

```python
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
    exclude_dynamic_sections: NotRequired[bool]
    snapshot: NotRequired[bool]
```

| Field                      | Required | Description                                                                                                                                                                                                                                                                                   |
|:---------------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type`                     | Yes      | Must be `"preset"` to use a preset system prompt                                                                                                                                                                                                                                              |
| `preset`                   | Yes      | Must be `"claude_code"` to use Claude Code’s system prompt                                                                                                                                                                                                                                    |
| `append`                   | No       | Additional instructions to append to the preset system prompt                                                                                                                                                                                                                                 |
| `exclude_dynamic_sections` | No       | Move per-user context, such as the auto memory location, from the system prompt into the first user message. Improves prompt-cache reuse across users and machines. See [Modify system prompts](agent-sdk-modifying-system-prompts.md#improve-prompt-caching-across-users-and-machines) |
| `snapshot`                 | No       | Set to `False` to rebuild the system prompt on every request instead of [reusing the prompt the session recorded on its first request](agent-sdk-modifying-system-prompts.md#change-the-prompt-of-an-existing-session). Requires `claude-agent-sdk` v0.2.153 or later                   |


[​](#systempromptcustom)

`SystemPromptCustom`

A custom system prompt in object form, equivalent to passing a string as `system_prompt`, that can also set `snapshot`. Requires `claude-agent-sdk` v0.2.153 or later.

```python
class SystemPromptCustom(TypedDict):
    type: Literal["custom"]
    prompt: str
    snapshot: NotRequired[bool]
```

| Field      | Required | Description                                                                                                                        |
|:-----------|:---------|:-----------------------------------------------------------------------------------------------------------------------------------|
| `type`     | Yes      | Must be `"custom"`                                                                                                                 |
| `prompt`   | Yes      | The system prompt text. Passed to the CLI as a command-line argument, so the [command-line length limits](#systempromptfile) apply |
| `snapshot` | No       | Same as [`SystemPromptPreset.snapshot`](#systempromptpreset), applied to `prompt`                                                  |


[​](#systempromptfile)

`SystemPromptFile`

Configuration for loading a custom system prompt from a file instead of passing it as a string. The SDK maps this to the CLI [`--system-prompt-file`](../02-Claude-Code-CLI/cli-reference.md#system-prompt-flags) flag. Use the file form when the prompt is large: the SDK passes a string `system_prompt` on the CLI subprocess argv, which is subject to OS command-line length limits before the SDK sends any API request. On Linux a single argument longer than roughly 128 KB fails at process spawn with `Argument list too long`. On Windows the whole command line is capped at roughly 32 KB, so the string form fails at a lower threshold.

```python
class SystemPromptFile(TypedDict):
    type: Literal["file"]
    path: str
```

| Field  | Required | Description                                   |
|:-------|:---------|:----------------------------------------------|
| `type` | Yes      | Must be `"file"` to load the prompt from disk |
| `path` | Yes      | Path to a file containing the system prompt   |


[​](#settingsource)

`SettingSource`

Controls which filesystem-based configuration sources the SDK loads settings from.

```python
SettingSource = Literal["user", "project", "local"]
```

| Value       | Description                                                               | Location                      |
|:------------|:--------------------------------------------------------------------------|:------------------------------|
| `"user"`    | Global user settings                                                      | `~/.claude/settings.json`     |
| `"project"` | Shared project settings (version controlled)                              | `.claude/settings.json`       |
| `"local"`   | Local project settings, gitignored when Claude Code saves a setting to it | `.claude/settings.local.json` |


[​](#default-behavior)

Default behavior

When `setting_sources` is omitted or `None` and `skills` is not set, `query()` loads the same filesystem settings as the Claude Code CLI: user, project, and local. With `skills` set, the [`setting_sources`](#claudeagentoptions) row describes the current default. Endpoint-managed policy is loaded in all cases; server-managed settings are fetched when the session authenticates with an organization credential on an [eligible configuration](../13-Enterprise-Admin/server-managed-settings.md#platform-availability). For more information, see [What settingSources does not control](agent-sdk-claude-code-features.md#what-settingsources-does-not-control).


[​](#why-use-setting_sources)

Why use setting_sources

**Disable filesystem settings:**

```python
# Do not load user, project, or local settings from disk
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Analyze this code",
        options=ClaudeAgentOptions(
            setting_sources=[]
        ),
    ):
        print(message)


asyncio.run(main())
```

In Python SDK 0.1.59 and earlier, an empty list was treated the same as omitting the option, so `setting_sources=[]` did not disable filesystem settings. Upgrade to a newer release if you need an empty list to take effect. The TypeScript SDK is not affected.

**Load only specific setting sources:**

```python
# Load only project settings, ignore user and local
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Run CI checks",
        options=ClaudeAgentOptions(
            setting_sources=["project"]  # Only .claude/settings.json
        ),
    ):
        print(message)


asyncio.run(main())
```

**SDK-only applications:**

```python
# Define everything programmatically.
# Pass [] to opt out of filesystem setting sources.
import asyncio
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions, query


async def main():
    async for message in query(
        prompt="Review this PR",
        options=ClaudeAgentOptions(
            setting_sources=[],
            agents={
                "code-reviewer": AgentDefinition(
                    description="Reviews code changes",
                    prompt="You are a code reviewer. Report issues in the diff.",
                ),
            },
            allowed_tools=["Read", "Grep", "Glob"],
        ),
    ):
        print(message)


asyncio.run(main())
```

To load CLAUDE.md project instructions, include `"project"` in `setting_sources`. See [Modify system prompts](agent-sdk-modifying-system-prompts.md#claude-md-files-for-project-level-instructions) for how CLAUDE.md loading interacts with the system prompt options.


[​](#settings-precedence)

Settings precedence

When multiple sources are loaded, settings are merged with this precedence (highest to lowest):

1.  Local settings (`.claude/settings.local.json`)
2.  Project settings (`.claude/settings.json`)
3.  User settings (`~/.claude/settings.json`)

Programmatic options such as `agents`, `allowed_tools`, and `settings` override user, project, and local filesystem settings. Managed policy settings take precedence over programmatic options.


[​](#agentdefinition)

`AgentDefinition`

Configuration for a subagent defined programmatically.

```python
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    disallowedTools: list[str] | None = None
    model: str | None = None
    skills: list[str] | None = None
    memory: Literal["user", "project", "local"] | None = None
    mcpServers: list[str | dict[str, Any]] | None = None
    initialPrompt: str | None = None
    maxTurns: int | None = None
    background: bool | None = None
    effort: EffortLevel | int | None = None
    permissionMode: PermissionMode | None = None
```

| Field             | Required | Description                                                                                                                                                                                                                                  |
|:------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `description`     | Yes      | Natural language description of when to use this agent                                                                                                                                                                                       |
| `prompt`          | Yes      | The agent’s system prompt                                                                                                                                                                                                                    |
| `tools`           | No       | Array of allowed tool names. If omitted, inherits every [tool available to subagents](../09-Agents-Patterns/sub-agents.md#available-tools)                                                                                                                   |
| `disallowedTools` | No       | Array of tool names to remove from the agent’s tool set. MCP server-level patterns are also accepted: `mcp__server` or `mcp__server__*` removes every tool from that server, and `mcp__*` removes every MCP tool from any server             |
| `model`           | No       | Model override for this agent. Accepts an alias such as `"sonnet"`, `"opus"`, `"haiku"`, or `"inherit"`, or a full model ID. When you omit it, Claude Code picks the model in the [subagent model order](../09-Agents-Patterns/sub-agents.md#choose-a-model) |
| `skills`          | No       | List of skill names to preload into the agent’s context at startup. Unlisted skills remain invocable through the Skill tool                                                                                                                  |
| `memory`          | No       | Memory source for this agent: `"user"`, `"project"`, or `"local"`                                                                                                                                                                            |
| `mcpServers`      | No       | MCP servers available to this agent. Each entry is a server name or an inline `{name: config}` dict                                                                                                                                          |
| `initialPrompt`   | No       | Auto-submitted as the first user turn when this agent runs as the main thread agent                                                                                                                                                          |
| `maxTurns`        | No       | Maximum number of agentic turns before the agent stops                                                                                                                                                                                       |
| `background`      | No       | Run this agent as a non-blocking background task when invoked                                                                                                                                                                                |
| `effort`          | No       | Reasoning effort level for this agent. Accepts a named level or an integer. See [`EffortLevel`](#effortlevel)                                                                                                                                |
| `permissionMode`  | No       | Permission mode for tool execution within this agent. The [subagent inheritance rules](agent-sdk-permissions.md#available-modes) decide when it applies. See [`PermissionMode`](#permissionmode)                                       |

`AgentDefinition` field names use camelCase, such as `disallowedTools`, `permissionMode`, and `maxTurns`. These names map directly to the wire format shared with the TypeScript SDK. This differs from `ClaudeAgentOptions`, which uses Python snake_case for the equivalent top-level fields such as `disallowed_tools` and `permission_mode`. Because `AgentDefinition` is a dataclass, passing a snake_case keyword raises a `TypeError` at construction time.


[​](#permissionmode)

`PermissionMode`

Permission modes for controlling tool execution.

```python
PermissionMode = Literal[
    "default",  # Standard permission behavior
    "acceptEdits",  # Auto-accept file edits
    "plan",  # Planning mode - explore without editing
    "dontAsk",  # Deny anything not pre-approved instead of prompting
    "bypassPermissions",  # Bypass permission checks; explicit ask rules still prompt (use with caution)
    "auto",  # A model classifier reviews actions such as shell commands and network requests
]
```


[​](#effortlevel)

`EffortLevel`

Effort levels for guiding thinking depth.

```python
EffortLevel = Literal[
    "low",  # Minimal thinking, fastest responses
    "medium",  # Moderate thinking
    "high",  # Deep reasoning
    "xhigh",  # Extended reasoning; falls back to "high" on models that don't support it
    "max",  # Maximum effort
]
```


[​](#canusetool)

`CanUseTool`

Type alias for tool permission callback functions.

```python
CanUseTool = Callable[
    [str, dict[str, Any], ToolPermissionContext], Awaitable[PermissionResult]
]
```

The callback receives:

- `tool_name`: Name of the tool being called
- `input_data`: The tool’s input parameters
- `context`: A `ToolPermissionContext` with additional information

Returns a `PermissionResult` (either `PermissionResultAllow` or `PermissionResultDeny`). The callback is the SDK replacement for the interactive permission prompt: it’s invoked only when the [permission evaluation flow](agent-sdk-permissions.md#how-permissions-are-evaluated) resolves to a prompt. Tool calls already approved by an `allowed_tools` entry, a settings allow rule, or the permission mode, such as `acceptEdits` or `bypassPermissions`, never invoke it. To gate every tool call, use a [`PreToolUse` hook](agent-sdk-hooks.md) instead. An allow rule doesn’t pre-approve the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves); see [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) for which of them reach the callback and what happens in `dontAsk` and `auto` mode.


[​](#toolpermissioncontext)

`ToolPermissionContext`

Context information passed to tool permission callbacks.

```python
@dataclass
class ToolPermissionContext:
    signal: Any | None = None  # Future: abort signal support
    suggestions: list[PermissionUpdate] = field(default_factory=list)
    tool_use_id: str | None = None
    agent_id: str | None = None
    blocked_path: str | None = None
    decision_reason: str | None = None
    title: str | None = None
    display_name: str | None = None
    description: str | None = None
```

| Field             | Type                     | Description                                                                                                                                                                                                                                 |
|:------------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `signal`          | `Any | None`             | Reserved for future abort signal support                                                                                                                                                                                                    |
| `suggestions`     | `list[PermissionUpdate]` | Permission update suggestions from the CLI. Bash prompts include a suggestion with the `localSettings` destination, so returning it in `updated_permissions` writes the rule to `.claude/settings.local.json` and persists across sessions. |
| `tool_use_id`     | `str | None`             | Identifier of the specific tool call this prompt is for. Always populated when delivered to `can_use_tool`                                                                                                                                  |
| `agent_id`        | `str | None`             | Sub-agent ID when the call originates from a subagent; `None` for the main agent                                                                                                                                                            |
| `blocked_path`    | `str | None`             | File path that triggered the permission request, when applicable. For example, when a Bash command tries to access a path outside allowed directories                                                                                       |
| `decision_reason` | `str | None`             | Reason this permission request was triggered. Forwarded from a PreToolUse hook’s `permissionDecisionReason` when the hook returned `"ask"`                                                                                                  |
| `title`           | `str | None`             | Full permission prompt sentence, such as `Claude wants to read foo.txt`. Use as the primary prompt text when present                                                                                                                        |
| `display_name`    | `str | None`             | Short noun phrase for the tool action, such as `Read file`, suitable for button labels                                                                                                                                                      |
| `description`     | `str | None`             | Human-readable subtitle for the permission UI                                                                                                                                                                                               |


[​](#permissionresult)

`PermissionResult`

Union type for permission callback results.

```python
PermissionResult = PermissionResultAllow | PermissionResultDeny
```


[​](#permissionresultallow)

`PermissionResultAllow`

Result indicating the tool call should be allowed.

```python
@dataclass
class PermissionResultAllow:
    behavior: Literal["allow"] = "allow"
    updated_input: dict[str, Any] | None = None
    updated_permissions: list[PermissionUpdate] | None = None
```

| Field                 | Type                            | Default   | Description                               |
|:----------------------|:--------------------------------|:----------|:------------------------------------------|
| `behavior`            | `Literal["allow"]`              | `"allow"` | Must be “allow”                           |
| `updated_input`       | `dict[str, Any] | None`         | `None`    | Modified input to use instead of original |
| `updated_permissions` | `list[PermissionUpdate] | None` | `None`    | Permission updates to apply               |


[​](#permissionresultdeny)

`PermissionResultDeny`

Result indicating the tool call should be denied.

```python
@dataclass
class PermissionResultDeny:
    behavior: Literal["deny"] = "deny"
    message: str = ""
    interrupt: bool = False
```

| Field       | Type              | Default  | Description                                |
|:------------|:------------------|:---------|:-------------------------------------------|
| `behavior`  | `Literal["deny"]` | `"deny"` | Must be “deny”                             |
| `message`   | `str`             | `""`     | Message explaining why the tool was denied |
| `interrupt` | `bool`            | `False`  | Whether to interrupt the current execution |


[​](#permissionupdate)

`PermissionUpdate`

Configuration for updating permissions programmatically.

```python
@dataclass
class PermissionUpdate:
    type: Literal[
        "addRules",
        "replaceRules",
        "removeRules",
        "setMode",
        "addDirectories",
        "removeDirectories",
    ]
    rules: list[PermissionRuleValue] | None = None
    behavior: Literal["allow", "deny", "ask"] | None = None
    mode: PermissionMode | None = None
    directories: list[str] | None = None
    destination: (
        Literal["userSettings", "projectSettings", "localSettings", "session"] | None
    ) = None
```

| Field         | Type                                     | Description                                     |
|:--------------|:-----------------------------------------|:------------------------------------------------|
| `type`        | `Literal[...]`                           | The type of permission update operation         |
| `rules`       | `list[PermissionRuleValue] | None`       | Rules for add/replace/remove operations         |
| `behavior`    | `Literal["allow", "deny", "ask"] | None` | Behavior for rule-based operations              |
| `mode`        | `PermissionMode | None`                  | Mode for setMode operation                      |
| `directories` | `list[str] | None`                       | Directories for add/remove directory operations |
| `destination` | `Literal[...] | None`                    | Where to apply the permission update            |


[​](#permissionrulevalue)

`PermissionRuleValue`

A rule to add, replace, or remove in a permission update.

```python
@dataclass
class PermissionRuleValue:
    tool_name: str
    rule_content: str | None = None
```


[​](#toolspreset)

`ToolsPreset`

Preset tools configuration for using Claude Code’s default tool set.

```python
class ToolsPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
```


[​](#thinkingconfig)

`ThinkingConfig`

Controls extended thinking behavior. A union of three configurations:

```python
ThinkingDisplay = Literal["summarized", "omitted"]


class ThinkingConfigAdaptive(TypedDict):
    type: Literal["adaptive"]
    display: NotRequired[ThinkingDisplay]


class ThinkingConfigEnabled(TypedDict):
    type: Literal["enabled"]
    budget_tokens: int
    display: NotRequired[ThinkingDisplay]


class ThinkingConfigDisabled(TypedDict):
    type: Literal["disabled"]


ThinkingConfig = ThinkingConfigAdaptive | ThinkingConfigEnabled | ThinkingConfigDisabled
```

| Variant    | Fields                             | Description                                  |
|:-----------|:-----------------------------------|:---------------------------------------------|
| `adaptive` | `type`, `display`                  | Claude adaptively decides when to think      |
| `enabled`  | `type`, `budget_tokens`, `display` | Enable thinking with a specific token budget |
| `disabled` | `type`                             | Disable thinking                             |

The optional `display` field controls whether thinking text is returned `"summarized"` or `"omitted"`. On Claude Opus 4.7 and later, the API default is `"omitted"`, so set `"summarized"` to receive thinking content in [`ThinkingBlock`](#thinkingblock) outputs. Claude Code doesn’t pass your `display` value to some providers, such as Amazon Bedrock and Google Cloud’s Agent Platform. On those providers, Opus 4.7 and later return empty `ThinkingBlock` outputs even when you set `display` to `"summarized"`. Because these are `TypedDict` classes, they’re plain dicts at runtime. Either construct them as dict literals or call the class like a constructor; both produce a `dict`. Access fields with `config["budget_tokens"]`, not `config.budget_tokens`:

```python
from claude_agent_sdk import ClaudeAgentOptions, ThinkingConfigEnabled

# Option 1: dict literal (recommended, no import needed)
options = ClaudeAgentOptions(thinking={"type": "enabled", "budget_tokens": 20000})

# Option 2: constructor-style (returns a plain dict)
config = ThinkingConfigEnabled(type="enabled", budget_tokens=20000)
print(config["budget_tokens"])  # 20000
# config.budget_tokens would raise AttributeError
```


[​](#taskbudget)

`TaskBudget`

API-side task budget in tokens, used with the `task_budget` field in `ClaudeAgentOptions`.

```python
class TaskBudget(TypedDict):
    total: int
```

| Field   | Type  | Description                     |
|:--------|:------|:--------------------------------|
| `total` | `int` | Total token budget for the task |

Because this is a `TypedDict`, pass it as a plain dict, such as `ClaudeAgentOptions(task_budget={"total": 50000})`.


[​](#sdkbeta)

`SdkBeta`

Literal type for SDK beta features.

```python
SdkBeta = Literal["context-1m-2025-08-07"]
```

Use with the `betas` field in `ClaudeAgentOptions` to enable beta features.

On the Claude API, the `context-1m-2025-08-07` beta is retired for Claude Sonnet 4.5 and Claude Sonnet 4. If you still pass it with either model, requests that exceed the standard 200K-token context window return an error, so remove it from `betas`. To run a session with a 1M-token context window, set `model` to a model that [runs with the 1M window by default](../02-Claude-Code-CLI/model-config.md#extended-context), such as `claude-sonnet-5-5` or `claude-opus-5-5`. For a model that reaches 1M only through its `[1m]` variant, append the suffix to the model ID, as in `claude-opus-4-6[1m]`.


[​](#mcpsdkserverconfig)

`McpSdkServerConfig`

Configuration for SDK MCP servers created with `create_sdk_mcp_server()`.

```python
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server instance
```


[​](#mcpserverconfig)

`McpServerConfig`

Union type for MCP server configurations.

```python
McpServerConfig = (
    McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig
)
```


[​](#mcpstdioserverconfig)

`McpStdioServerConfig`

```python
class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Optional for backwards compatibility
    command: str
    args: NotRequired[list[str]]
    env: NotRequired[dict[str, str]]
```


[​](#mcpsseserverconfig)

`McpSSEServerConfig`

```python
class McpSSEServerConfig(TypedDict):
    type: Literal["sse"]
    url: str
    headers: NotRequired[dict[str, str]]
```


[​](#mcphttpserverconfig)

`McpHttpServerConfig`

```python
class McpHttpServerConfig(TypedDict):
    type: Literal["http"]
    url: str
    headers: NotRequired[dict[str, str]]
```


[​](#mcpserverstatusconfig)

`McpServerStatusConfig`

The configuration of an MCP server as reported by [`get_mcp_status()`](#methods). This is the union of all [`McpServerConfig`](#mcpserverconfig) transport variants plus an output-only `claudeai-proxy` variant for servers proxied through claude.ai.

```python
McpServerStatusConfig = (
    McpStdioServerConfig
    | McpSSEServerConfig
    | McpHttpServerConfig
    | McpSdkServerConfigStatus
    | McpClaudeAIProxyServerConfig
)
```

`McpSdkServerConfigStatus` is the serializable form of [`McpSdkServerConfig`](#mcpsdkserverconfig) with only `type` (`"sdk"`) and `name` (`str`) fields; the in-process `instance` is omitted. `McpClaudeAIProxyServerConfig` has `type` (`"claudeai-proxy"`), `url` (`str`), and `id` (`str`) fields.


[​](#mcpstatusresponse)

`McpStatusResponse`

Response from [`ClaudeSDKClient.get_mcp_status()`](#methods). Wraps the list of server statuses under the `mcpServers` key.

```python
class McpStatusResponse(TypedDict):
    mcpServers: list[McpServerStatus]
```


[​](#mcpserverstatus)

`McpServerStatus`

Status of a connected MCP server, contained in [`McpStatusResponse`](#mcpstatusresponse).

```python
class McpServerStatus(TypedDict):
    name: str
    status: McpServerConnectionStatus  # "connected" | "failed" | "needs-auth" | "pending" | "disabled"
    serverInfo: NotRequired[McpServerInfo]
    error: NotRequired[str]
    config: NotRequired[McpServerStatusConfig]
    scope: NotRequired[str]
    tools: NotRequired[list[McpToolInfo]]
```

| Field        | Type                                                         | Description                                                                                                                                                                   |
|:-------------|:-------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name`       | `str`                                                        | Server name                                                                                                                                                                   |
| `status`     | `str`                                                        | One of `"connected"`, `"failed"`, `"needs-auth"`, `"pending"`, or `"disabled"`                                                                                                |
| `serverInfo` | `dict` (optional)                                            | Server name and version (`{"name": str, "version": str}`)                                                                                                                     |
| `error`      | `str` (optional)                                             | Error message if the server failed to connect                                                                                                                                 |
| `config`     | [`McpServerStatusConfig`](#mcpserverstatusconfig) (optional) | Server configuration. Same shape as [`McpServerConfig`](#mcpserverconfig) (stdio, SSE, HTTP, or SDK), plus a `claudeai-proxy` variant for servers connected through claude.ai |
| `scope`      | `str` (optional)                                             | Configuration scope                                                                                                                                                           |
| `tools`      | `list` (optional)                                            | Tools provided by this server, each with `name`, `description`, and `annotations` fields                                                                                      |


[​](#contextusageresponse)

`ContextUsageResponse`

Response from [`ClaudeSDKClient.get_context_usage()`](#methods). This is the same payload Claude Code renders for the `/context` command in an interactive session, so alongside the token counts it carries display fields such as `color` and `gridRows` that Claude Code uses to draw the `/context` usage grid. Claude Code builds this payload by sending several requests to the [token-counting](../04-API-Reference/Guides/build-with-claude-token-counting.md) API. These requests don’t appear in the message stream, so cost tracking that reads the stream won’t see them. On the Anthropic API, token counting isn’t billed.

```python
class ContextUsageResponse(TypedDict):
    categories: list[ContextUsageCategory]
    totalTokens: int
    maxTokens: int
    rawMaxTokens: int
    percentage: float
    model: str
    isAutoCompactEnabled: bool
    memoryFiles: list[dict[str, Any]]
    mcpTools: list[dict[str, Any]]
    agents: list[dict[str, Any]]
    gridRows: list[list[dict[str, Any]]]
    autoCompactThreshold: NotRequired[int]
    deferredBuiltinTools: NotRequired[list[dict[str, Any]]]
    systemTools: NotRequired[list[dict[str, Any]]]
    systemPromptSections: NotRequired[list[dict[str, Any]]]
    slashCommands: NotRequired[dict[str, Any]]
    skills: NotRequired[dict[str, Any]]  # skill usage with frontmatter breakdown
    messageBreakdown: NotRequired[dict[str, Any]]  # message tokens by type
    apiUsage: NotRequired[dict[str, Any] | None]
```

Each `ContextUsageCategory` entry carries `name`, `tokens`, `color`, and an optional `isDeferred` flag. `totalTokens` is the session’s current context usage, and `maxTokens` is the window that usage is measured against. That window is the model’s context window, or the lower auto-compaction window when one applies, and `rawMaxTokens` carries the same value as `maxTokens`. `apiUsage` holds the usage from the latest API response, not a running total for the session. Claude Code leaves the optional `deferredBuiltinTools`, `systemTools`, and `systemPromptSections` keys unset, so expect them to be absent even though the type declares them.


[​](#sdkpluginconfig)

`SdkPluginConfig`

Configuration for loading plugins in the SDK.

```python
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
```

| Field  | Type               | Description                                                |
|:-------|:-------------------|:-----------------------------------------------------------|
| `type` | `Literal["local"]` | Must be `"local"` (only local plugins currently supported) |
| `path` | `str`              | Absolute or relative path to the plugin directory          |

**Example:**

```python
plugins = [
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"},
]
```

For complete information on creating and using plugins, see [Plugins](agent-sdk-plugins.md).


[​](#message-types)

Message Types


[​](#message)

`Message`

Union type of all possible messages.

```python
Message = (
    UserMessage
    | AssistantMessage
    | SystemMessage
    | ResultMessage
    | StreamEvent
    | RateLimitEvent
    | ConversationResetMessage
)
```


[​](#usermessage)

`UserMessage`

User input message.

```python
@dataclass
class UserMessage:
    content: str | list[ContentBlock]
    uuid: str | None = None
    parent_tool_use_id: str | None = None
    tool_use_result: dict[str, Any] | None = None
    origin: MessageOrigin | None = None
```

| Field                | Type                       | Description                                                                                                                                                                               |
|:---------------------|:---------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `content`            | `str | list[ContentBlock]` | Message content as text or content blocks                                                                                                                                                 |
| `uuid`               | `str | None`               | Unique message identifier                                                                                                                                                                 |
| `parent_tool_use_id` | `str | None`               | Tool use ID if this message is a tool result response                                                                                                                                     |
| `tool_use_result`    | `dict[str, Any] | None`    | Tool result data if applicable                                                                                                                                                            |
| `origin`             | `MessageOrigin | None`     | Provenance of this message, populated on injected turns such as task notifications and peer messages. `None` when the CLI didn’t attribute it. Requires Python Agent SDK 0.2.137 or later |

The SDK passes `tool_use_result` through from the CLI unmodified. For a tool on an external MCP server whose result contains `resource_link` blocks, the dict has a `resourceLinks` key holding a list of dicts with the keys of the TypeScript [`SDKMcpResourceLink`](agent-sdk-typescript.md#sdkmcpresourcelink) type. Claude receives each link as a line of text in the tool result. To render the files the server returned, read `resourceLinks` instead of parsing that text. The `resourceLinks` key requires Python Agent SDK 0.2.150 or later and Claude Code v2.1.257 or later; the CLI bundled with that SDK version satisfies the Claude Code requirement. The CLI omits the key when the result has no links and on results from subagents. The CLI keeps at most 50 links per result and stops adding links once the list reaches 64 KiB of serialized JSON. A tool you define in-process with [`tool()`](#tool) never produces the key, because the SDK flattens its `resource_link` blocks to text before the CLI sees the result.


[​](#assistantmessage)

`AssistantMessage`

Assistant response message with content blocks.

```python
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str
    parent_tool_use_id: str | None = None
    error: AssistantMessageError | None = None
    usage: dict[str, Any] | None = None
    message_id: str | None = None
    stop_reason: str | None = None
    session_id: str | None = None
    uuid: str | None = None
```

| Field                | Type                                                        | Description                                                                    |
|:---------------------|:------------------------------------------------------------|:-------------------------------------------------------------------------------|
| `content`            | `list[ContentBlock]`                                        | List of content blocks in the response                                         |
| `model`              | `str`                                                       | Model that generated the response                                              |
| `parent_tool_use_id` | `str | None`                                                | Tool use ID if this is a nested response                                       |
| `error`              | [`AssistantMessageError`](#assistantmessageerror) ` | None` | Error type if the response encountered an error                                |
| `usage`              | `dict[str, Any] | None`                                     | Per-message token usage (same keys as [`ResultMessage.usage`](#resultmessage)) |
| `message_id`         | `str | None`                                                | API message ID. Multiple messages from one turn share the same ID              |
| `stop_reason`        | `str | None`                                                | Stop reason from the API (for example, `end_turn`, `tool_use`)                 |
| `session_id`         | `str | None`                                                | ID of the session this message belongs to                                      |
| `uuid`               | `str | None`                                                | Unique message identifier within the session transcript                        |


[​](#assistantmessageerror)

`AssistantMessageError`

Possible error types for assistant messages.

```python
AssistantMessageError = Literal[
    "authentication_failed",
    "billing_error",
    "rate_limit",
    "invalid_request",
    "server_error",
    "unknown",
]
```

The underlying CLI process can emit error types this Literal doesn’t list, such as `max_output_tokens`. The SDK passes the value through unmodified, so treat strings outside this list the way you treat `unknown`. The TypeScript [`SDKAssistantMessageError`](agent-sdk-typescript.md#sdkassistantmessage) type lists the full set of values the CLI can emit.


[​](#systemmessage)

`SystemMessage`

System message with metadata.

```python
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]
```

Subtypes that have no dataclass of their own arrive as `SystemMessage`. To follow the session between turns, set [`CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1`](../02-Claude-Code-CLI/env-vars.md#variables) and read `message.data["state"]` on each message whose `subtype` is `session_state_changed`. [`SDKSessionStateChangedMessage`](agent-sdk-typescript.md#sdksessionstatechangedmessage) lists the states it can carry. Iterate with `receive_messages()` to read them: `receive_response()` stops at the `ResultMessage`, and a `session_state_changed` message can follow that result.


[​](#resultmessage)

`ResultMessage`

Final result message with cost and usage information.

```python
@dataclass
class ResultMessage:
    subtype: str
    duration_ms: int
    duration_api_ms: int
    is_error: bool
    num_turns: int
    session_id: str
    stop_reason: str | None = None
    total_cost_usd: float | None = None
    usage: dict[str, Any] | None = None
    result: str | None = None
    structured_output: Any = None
    model_usage: dict[str, ModelUsage] | None = None
    permission_denials: list[Any] | None = None
    deferred_tool_use: DeferredToolUse | None = None
    errors: list[str] | None = None
    api_error_status: int | None = None
    uuid: str | None = None
    terminal_reason: str | None = None
    origin: MessageOrigin | None = None
```

The `subtype` field determines which other fields are populated. It is one of `"success"`, `"error_during_execution"`, `"error_max_turns"`, `"error_max_budget_usd"`, or `"error_max_structured_output_retries"`. The Python dataclass flattens all variants into one shape, so fields that don’t apply to the returned subtype are `None`. Several fields carry diagnostic detail about how the conversation ended:

- `is_error`: `True` when the conversation ended in an error state. Always `True` on the `error_*` subtypes. On `subtype="success"` it is `True` when the final model request failed, meaning the agent loop completed but the last API call returned an error.
- `api_error_status`: the HTTP status code of the terminating API error. `None` when the turn ended without one. Populated only on `subtype="success"`.
- `result`: text of the final assistant message on `subtype="success"`, or `None` on the `error_*` subtypes. When `subtype="success"` and `is_error=True`, this holds the API error string if one is available but can be empty, so check `api_error_status` and the preceding `AssistantMessage` content for detail.
- `errors`: loop-level error strings such as the max-turns message. Populated only on the `error_*` subtypes.
- `terminal_reason`: why the query loop ended, such as `"completed"`, `"max_turns"`, `"api_error"`, `"aborted_streaming"`, or `"aborted_tools"`. A value of `"aborted_streaming"` or `"aborted_tools"` means the turn was aborted before completing. Common causes are [`interrupt()`](#claudesdkclient) and a permission callback returning [`PermissionResultDeny`](#permissionresultdeny) with `interrupt=True`. `None` on CLI versions that predate the field, on results from local commands such as `/voice` or `/usage`, which bypass the query loop, or on synthesized error results emitted when the session fails fatally. Mirrors the TypeScript SDK’s [`SDKResultMessage.terminal_reason`](agent-sdk-typescript.md#sdkresultmessage), which lists the full set of values.
- `origin`: origin of the user message that triggered this turn. In [streaming input mode](agent-sdk-streaming-vs-single-mode.md), check this to tell the result of your own prompt, where `origin` is `None` or `{"kind": "human"}`, from the result of an injected turn such as a background-task notification. Requires Python Agent SDK 0.2.137 or later.

The `usage` dict covers the main agent loop only and excludes subagent and other nested or auxiliary model calls. In [streaming input mode](agent-sdk-streaming-vs-single-mode.md), the values are per-turn. Prefer `model_usage` for token and cost accounting. The `usage` dict contains the following keys when present:

| Key                           | Type  | Description                                                                                                                                                                                        |
|-------------------------------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `input_tokens`                | `int` | Input tokens consumed by the top-level agent loop. [Subagent tokens aren’t included](agent-sdk-cost-tracking.md#get-the-total-cost-of-a-query); use `model_usage` for whole-tree accounting. |
| `output_tokens`               | `int` | Output tokens generated by the top-level agent loop. Subagent tokens aren’t included.                                                                                                              |
| `cache_creation_input_tokens` | `int` | Tokens used to create new cache entries.                                                                                                                                                           |
| `cache_read_input_tokens`     | `int` | Tokens read from existing cache entries.                                                                                                                                                           |

The `model_usage` dict maps model names to per-model usage. It covers every model call made through the query pipeline: the main loop, subagents, and internal calls such as compaction and Workflow agents. Helper calls outside that pipeline, such as the permission classifier and token-counting requests, are excluded from `model_usage`. Treat `model_usage` as an estimate, not a billing statement. In [streaming input mode](agent-sdk-streaming-vs-single-mode.md), `model_usage` and `total_cost_usd` are cumulative across turns, so read the latest result rather than summing across results. A call that resumes a session also counts the [totals restored from the session’s earlier calls](agent-sdk-cost-tracking.md#accumulate-costs-across-multiple-calls). See [Track costs in streaming input mode](agent-sdk-cost-tracking.md#track-costs-in-streaming-input-mode) for resets and [Recover totals after a session crash](agent-sdk-cost-tracking.md#recover-totals-after-a-session-crash) for zeroed results. Each value in `model_usage` is a `ModelUsage` TypedDict, imported via `from claude_agent_sdk.types import ModelUsage`. Its keys use camelCase because the SDK passes the value through unmodified from the underlying CLI process, matching the TypeScript [`ModelUsage`](agent-sdk-typescript.md#modelusage) type:

| Key                        | Type    | Description                                                                                                                                                                                                                                                                                                                            |
|----------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `inputTokens`              | `int`   | Input tokens for this model.                                                                                                                                                                                                                                                                                                           |
| `outputTokens`             | `int`   | Output tokens for this model.                                                                                                                                                                                                                                                                                                          |
| `cacheReadInputTokens`     | `int`   | Cache read tokens for this model.                                                                                                                                                                                                                                                                                                      |
| `cacheCreationInputTokens` | `int`   | Cache creation tokens for this model.                                                                                                                                                                                                                                                                                                  |
| `webSearchRequests`        | `int`   | Web search requests made by this model.                                                                                                                                                                                                                                                                                                |
| `thinkingTokens`           | `int`   | Thinking tokens generated by this model, already counted in `outputTokens`. Absent until a turn runs on a Claude Code version that records it, and not declared on the TypedDict, so read it with `.get()`. Requires Python Agent SDK 0.2.150 or later, whose bundled CLI records it.                                                  |
| `costUSD`                  | `float` | Estimated cost in USD for this model, computed client-side. See [Track cost and usage](agent-sdk-cost-tracking.md) for billing caveats.                                                                                                                                                                                          |
| `contextWindow`            | `int`   | Context window size for this model.                                                                                                                                                                                                                                                                                                    |
| `maxOutputTokens`          | `int`   | Maximum output token limit for this model.                                                                                                                                                                                                                                                                                             |
| `canonicalModel`           | `str`   | Canonical model ID used for the pricing lookup. May differ from the raw model string the entry is keyed by, such as a provider-specific ID or alias. Not always present.                                                                                                                                                               |
| `provider`                 | `str`   | API provider that served this model, such as `firstParty`, `bedrock`, `vertex`, `foundry`, `anthropicAws`, `mantle`, or `gateway`. Not always present.                                                                                                                                                                                 |
| `costBasis`                | `str`   | Price table that priced this model’s latest request: `list` for list price, `managed` for a [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) table, or `unknown` when neither matched the model ID. Not always present, and not declared on the TypedDict, so read it with `.get()`. Requires Claude Code v2.1.246 or later. |


[​](#streamevent)

`StreamEvent`

Stream event for partial message updates during streaming. Only received when `include_partial_messages=True` in `ClaudeAgentOptions`. Import via `from claude_agent_sdk.types import StreamEvent`.

```python
@dataclass
class StreamEvent:
    uuid: str
    session_id: str
    event: dict[str, Any]  # The raw Claude API stream event
    parent_tool_use_id: str | None = None
```

| Field                | Type             | Description                                                                                                                                                         |
|:---------------------|:-----------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `uuid`               | `str`            | Unique identifier for this event                                                                                                                                    |
| `session_id`         | `str`            | Session identifier                                                                                                                                                  |
| `event`              | `dict[str, Any]` | The raw Claude API stream event data                                                                                                                                |
| `parent_tool_use_id` | `str | None`     | Always `None`. Stream events are emitted for the main session only. For subagent attribution, use complete messages such as [`AssistantMessage`](#assistantmessage) |


[​](#ratelimitevent)

`RateLimitEvent`

Emitted when rate limit status changes (for example, from `"allowed"` to `"allowed_warning"`). Use this to warn users before they hit a hard limit, or to back off when status is `"rejected"`.

```python
@dataclass
class RateLimitEvent:
    rate_limit_info: RateLimitInfo
    uuid: str
    session_id: str
```

| Field             | Type                              | Description              |
|:------------------|:----------------------------------|:-------------------------|
| `rate_limit_info` | [`RateLimitInfo`](#ratelimitinfo) | Current rate limit state |
| `uuid`            | `str`                             | Unique event identifier  |
| `session_id`      | `str`                             | Session identifier       |


[​](#ratelimitinfo)

`RateLimitInfo`

Rate limit state carried by [`RateLimitEvent`](#ratelimitevent).

```python
RateLimitStatus = Literal["allowed", "allowed_warning", "rejected"]
RateLimitType = Literal[
    "five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet", "overage"
]


@dataclass
class RateLimitInfo:
    status: RateLimitStatus
    resets_at: int | None = None
    rate_limit_type: RateLimitType | None = None
    utilization: float | None = None
    overage_status: RateLimitStatus | None = None
    overage_resets_at: int | None = None
    overage_disabled_reason: str | None = None
    raw: dict[str, Any] = field(default_factory=dict)
```

| Field                     | Type                     | Description                                                                                                                                                     |
|:--------------------------|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `status`                  | `RateLimitStatus`        | Current status, one of `"allowed"`, `"allowed_warning"`, or `"rejected"`. `"allowed_warning"` means approaching the limit; `"rejected"` means the limit was hit |
| `resets_at`               | `int | None`             | Unix timestamp when the rate limit window resets                                                                                                                |
| `rate_limit_type`         | `RateLimitType | None`   | Which rate limit window applies                                                                                                                                 |
| `utilization`             | `float | None`           | Fraction of the rate limit consumed (0.0 to 1.0)                                                                                                                |
| `overage_status`          | `RateLimitStatus | None` | Status of pay-as-you-go overage usage, if applicable                                                                                                            |
| `overage_resets_at`       | `int | None`             | Unix timestamp when the overage window resets                                                                                                                   |
| `overage_disabled_reason` | `str | None`             | Why overage is unavailable, if status is `"rejected"`                                                                                                           |
| `raw`                     | `dict[str, Any]`         | Full raw dict from the CLI, including fields not modeled above                                                                                                  |


[​](#conversationresetmessage)

`ConversationResetMessage`

Emitted when the conversation is replaced without ending the connection, such as after `/clear`. See [Track costs in streaming input mode](agent-sdk-cost-tracking.md#track-costs-in-streaming-input-mode) for how a reset affects the running totals on later `ResultMessage` objects. Requires Python Agent SDK 0.2.137 or later.

```python
@dataclass
class ConversationResetMessage:
    new_conversation_id: str
    uuid: str
    session_id: str
```

| Field                 | Type  | Description                                                                                                                |
|:----------------------|:------|:---------------------------------------------------------------------------------------------------------------------------|
| `new_conversation_id` | `str` | Opaque identifier for the fresh conversation. Not the `session_id` of subsequent messages; read that from the next message |
| `uuid`                | `str` | Unique message identifier                                                                                                  |
| `session_id`          | `str` | ID of the session that was reset. Messages after the reset carry a new `session_id`                                        |


[​](#taskstartedmessage)

`TaskStartedMessage`

Emitted when a background task starts. A background task is anything tracked outside the main turn: a backgrounded Bash command, a [Monitor](#monitor) watch, a subagent spawned via the Agent tool, or a remote agent. The `task_type` field tells you which. This naming is unrelated to the `Task`-to-`Agent` tool rename.

```python
@dataclass
class TaskStartedMessage(SystemMessage):
    task_id: str
    description: str
    uuid: str
    session_id: str
    tool_use_id: str | None = None
    task_type: str | None = None
```

| Field         | Type         | Description                                                                                                                 |
|:--------------|:-------------|:----------------------------------------------------------------------------------------------------------------------------|
| `task_id`     | `str`        | Unique identifier for the task                                                                                              |
| `description` | `str`        | Description of the task                                                                                                     |
| `uuid`        | `str`        | Unique message identifier                                                                                                   |
| `session_id`  | `str`        | Session identifier                                                                                                          |
| `tool_use_id` | `str | None` | Associated tool use ID                                                                                                      |
| `task_type`   | `str | None` | Which kind of background task: `"local_bash"` for background Bash and Monitor watches, `"local_agent"`, or `"remote_agent"` |


[​](#taskusage)

`TaskUsage`

Token and timing data for a background task.

```python
class TaskUsage(TypedDict):
    total_tokens: int
    tool_uses: int
    duration_ms: int
```


[​](#taskprogressmessage)

`TaskProgressMessage`

Emitted periodically with progress updates for a running background task.

```python
@dataclass
class TaskProgressMessage(SystemMessage):
    task_id: str
    description: str
    usage: TaskUsage
    uuid: str
    session_id: str
    tool_use_id: str | None = None
    last_tool_name: str | None = None
```

| Field            | Type         | Description                         |
|:-----------------|:-------------|:------------------------------------|
| `task_id`        | `str`        | Unique identifier for the task      |
| `description`    | `str`        | Current status description          |
| `usage`          | `TaskUsage`  | Token usage for this task so far    |
| `uuid`           | `str`        | Unique message identifier           |
| `session_id`     | `str`        | Session identifier                  |
| `tool_use_id`    | `str | None` | Associated tool use ID              |
| `last_tool_name` | `str | None` | Name of the last tool the task used |


[​](#tasknotificationmessage)

`TaskNotificationMessage`

Emitted when a background task completes, fails, or is stopped. Background tasks include `run_in_background` Bash commands, Monitor watches, and background subagents.

```python
@dataclass
class TaskNotificationMessage(SystemMessage):
    task_id: str
    status: TaskNotificationStatus  # "completed" | "failed" | "stopped"
    output_file: str
    summary: str
    uuid: str
    session_id: str
    tool_use_id: str | None = None
    usage: TaskUsage | None = None
```

| Field         | Type                     | Description                                      |
|:--------------|:-------------------------|:-------------------------------------------------|
| `task_id`     | `str`                    | Unique identifier for the task                   |
| `status`      | `TaskNotificationStatus` | One of `"completed"`, `"failed"`, or `"stopped"` |
| `output_file` | `str`                    | Path to the task output file                     |
| `summary`     | `str`                    | Summary of the task result                       |
| `uuid`        | `str`                    | Unique message identifier                        |
| `session_id`  | `str`                    | Session identifier                               |
| `tool_use_id` | `str | None`             | Associated tool use ID                           |
| `usage`       | `TaskUsage | None`       | Final token usage for the task                   |

When the CLI [moves a long MCP tool call to the background](../06-MCP-Tools/General/mcp.md#automatic-backgrounding-of-long-tool-calls), the tool result for that call holds only a placeholder and the call’s real result arrives in this message. On a `"completed"` notification for such a call, the CLI adds a `resource_links` key listing the files the tool returned by reference, with the same entries and limits as the `resourceLinks` key on [`UserMessage.tool_use_result`](#usermessage). The `resource_links` key requires Python Agent SDK 0.2.150 or later and Claude Code v2.1.257 or later; the CLI bundled with that SDK version satisfies the Claude Code requirement. The dataclass has no field for `resource_links`. Read it from the `data` dict the message inherits from [`SystemMessage`](#systemmessage): `message.data.get("resource_links")`. Match the notification to the call with `tool_use_id`. The CLI omits the key when the result had no links and on notifications for tasks that aren’t MCP tool calls.


[​](#content-block-types)

Content Block Types


[​](#contentblock)

`ContentBlock`

Union type of all content blocks.

```python
ContentBlock = (
    TextBlock
    | ThinkingBlock
    | ToolUseBlock
    | ToolResultBlock
    | ServerToolUseBlock
    | ServerToolResultBlock
)
```


[​](#textblock)

`TextBlock`

Text content block.

```python
@dataclass
class TextBlock:
    text: str
```


[​](#thinkingblock)

`ThinkingBlock`

Thinking content block (for models with thinking capability).

```python
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str
```


[​](#tooluseblock)

`ToolUseBlock`

Tool use request block.

```python
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]
```


[​](#toolresultblock)

`ToolResultBlock`

Tool execution result block.

```python
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None
```


[​](#error-types)

Error Types

The types below define what your code catches. For entries keyed to the error messages these types raise, with the cause and fix for each, see [Troubleshooting](agent-sdk-troubleshooting.md).


[​](#claudesdkerror)

`ClaudeSDKError`

Base exception class for all SDK errors.

```python
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""
```

When a single-shot `query()` ends with an error result, for example a turn-limit error, the SDK raises a [`ResultError`](#resulterror).


[​](#clinotfounderror)

`CLINotFoundError`

Raised when Claude Code CLI is not installed or not found.

```python
class CLINotFoundError(CLIConnectionError):
    def __init__(
        self, message: str = "Claude Code not found", cli_path: str | None = None
    ):
        """
        Args:
            message: Error message (default: "Claude Code not found")
            cli_path: Optional path to the CLI that was not found
        """
```


[​](#cliconnectionerror)

`CLIConnectionError`

Raised when connection to Claude Code fails.

```python
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to Claude Code."""
```


[​](#processerror)

`ProcessError`

Raised when the Claude Code process fails.

```python
class ProcessError(ClaudeSDKError):
    def __init__(
        self, message: str, exit_code: int | None = None, stderr: str | None = None
    ):
        self.exit_code = exit_code
        self.stderr = stderr
```


[​](#resulterror)

`ResultError`

Raised when the Claude Code process exits because the run ended with an error [result message](#resultmessage), such as a turn-limit error or an API error. `ResultError` subclasses `ProcessError`, so an existing `except ProcessError` handler also catches it. Its attributes carry the fields of that result message, so you can branch on why the run failed without parsing the message text. Requires Python Agent SDK 0.2.140 or later.

```python
class ResultError(ProcessError):
    subtype: str | None  # "error_max_turns", "error_during_execution", ...; "success" when the run ended on a failed request
    errors: list[str]  # an empty list when the result message reported none
    result: str | None
    api_error_status: int | None
    terminal_reason: str | None  # "max_turns", "api_error", ...; check this before subtype
    session_id: str | None
    data: dict[str, Any]  # the raw result message payload
```

To tell failures apart, check `terminal_reason` before `subtype`. When the final request fails, such as on an API error, Claude Code reports `subtype` `"success"` with the cause in `terminal_reason`, for example `"api_error"`; when a limit you set ends the run, such as `max_turns` or `max_budget_usd`, it reports an `error_*` subtype.


[​](#clijsondecodeerror)

`CLIJSONDecodeError`

Raised when JSON parsing fails.

```python
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: The line that failed to parse
            original_error: The original JSON decode exception
        """
        self.line = line
        self.original_error = original_error
```


[​](#hook-types)

Hook Types

For a comprehensive guide on using hooks with examples and common patterns, see the [Hooks guide](agent-sdk-hooks.md).


[​](#hookevent)

`HookEvent`

Supported hook event types.

```python
HookEvent = Literal[
    "PreToolUse",  # Called before tool execution
    "PostToolUse",  # Called after tool execution
    "PostToolUseFailure",  # Called when a tool execution fails
    "UserPromptSubmit",  # Called when a prompt is submitted
    "Stop",  # Called when stopping execution
    "SubagentStop",  # Called when a subagent stops
    "PreCompact",  # Called before message compaction
    "Notification",  # Called for notification events
    "SubagentStart",  # Called when a subagent starts
    "PermissionRequest",  # Called when a permission decision is needed
]
```

The TypeScript SDK supports additional hook events not yet available in Python. See the [hook availability table](agent-sdk-hooks.md#available-hooks) for per-SDK support.


[​](#hookcallback)

`HookCallback`

Type definition for hook callback functions.

```python
HookCallback = Callable[[HookInput, str | None, HookContext], Awaitable[HookJSONOutput]]
```

Parameters:

- `input`: Strongly typed hook input with discriminated unions based on `hook_event_name` (see [`HookInput`](#hookinput))
- `tool_use_id`: Optional tool use identifier (for tool-related hooks)
- `context`: Hook context with additional information

Returns a [`HookJSONOutput`](#hookjsonoutput).


[​](#hookcontext)

`HookContext`

Context information passed to hook callbacks.

```python
class HookContext(TypedDict):
    signal: Any | None  # Future: abort signal support
```


[​](#hookmatcher)

`HookMatcher`

Configuration for matching hooks to specific events or tools.

```python
@dataclass
class HookMatcher:
    matcher: str | None = (
        None  # Tool name or pattern to match (e.g., "Bash", "Write|Edit")
    )
    hooks: list[HookCallback] = field(
        default_factory=list
    )  # List of callbacks to execute
    timeout: float | None = (
        None  # Timeout in seconds. When omitted, the per-event default applies:
        # 600 for most events, 30 for UserPromptSubmit
    )
```


[​](#hookinput)

`HookInput`

Union type of all hook input types. The actual type depends on the `hook_event_name` field.

```python
HookInput = (
    PreToolUseHookInput
    | PostToolUseHookInput
    | PostToolUseFailureHookInput
    | UserPromptSubmitHookInput
    | StopHookInput
    | SubagentStopHookInput
    | PreCompactHookInput
    | NotificationHookInput
    | SubagentStartHookInput
    | PermissionRequestHookInput
)
```


[​](#basehookinput)

`BaseHookInput`

Base fields present in all hook input types.

```python
class BaseHookInput(TypedDict):
    session_id: str
    transcript_path: str
    cwd: str
    permission_mode: NotRequired[str]
```

| Field             | Type             | Description                         |
|:------------------|:-----------------|:------------------------------------|
| `session_id`      | `str`            | Current session identifier          |
| `transcript_path` | `str`            | Path to the session transcript file |
| `cwd`             | `str`            | Current working directory           |
| `permission_mode` | `str` (optional) | Current permission mode             |


[​](#pretoolusehookinput)

`PreToolUseHookInput`

Input data for `PreToolUse` hook events.

```python
class PreToolUseHookInput(BaseHookInput):
    hook_event_name: Literal["PreToolUse"]
    tool_name: str
    tool_input: dict[str, Any]
    tool_use_id: str
    agent_id: NotRequired[str]
    agent_type: NotRequired[str]
```

| Field             | Type                    | Description                                                        |
|:------------------|:------------------------|:-------------------------------------------------------------------|
| `hook_event_name` | `Literal["PreToolUse"]` | Always “PreToolUse”                                                |
| `tool_name`       | `str`                   | Name of the tool about to be executed                              |
| `tool_input`      | `dict[str, Any]`        | Input parameters for the tool                                      |
| `tool_use_id`     | `str`                   | Unique identifier for this tool use                                |
| `agent_id`        | `str` (optional)        | Subagent identifier, present when the hook fires inside a subagent |
| `agent_type`      | `str` (optional)        | Subagent type, present when the hook fires inside a subagent       |


[​](#posttoolusehookinput)

`PostToolUseHookInput`

Input data for `PostToolUse` hook events.

```python
class PostToolUseHookInput(BaseHookInput):
    hook_event_name: Literal["PostToolUse"]
    tool_name: str
    tool_input: dict[str, Any]
    tool_response: Any
    tool_use_id: str
    agent_id: NotRequired[str]
    agent_type: NotRequired[str]
```

| Field             | Type                     | Description                                                        |
|:------------------|:-------------------------|:-------------------------------------------------------------------|
| `hook_event_name` | `Literal["PostToolUse"]` | Always “PostToolUse”                                               |
| `tool_name`       | `str`                    | Name of the tool that was executed                                 |
| `tool_input`      | `dict[str, Any]`         | Input parameters that were used                                    |
| `tool_response`   | `Any`                    | Response from the tool execution                                   |
| `tool_use_id`     | `str`                    | Unique identifier for this tool use                                |
| `agent_id`        | `str` (optional)         | Subagent identifier, present when the hook fires inside a subagent |
| `agent_type`      | `str` (optional)         | Subagent type, present when the hook fires inside a subagent       |


[​](#posttoolusefailurehookinput)

`PostToolUseFailureHookInput`

Input data for `PostToolUseFailure` hook events. Called when a tool execution fails.

```python
class PostToolUseFailureHookInput(BaseHookInput):
    hook_event_name: Literal["PostToolUseFailure"]
    tool_name: str
    tool_input: dict[str, Any]
    tool_use_id: str
    error: str
    is_interrupt: NotRequired[bool]
    agent_id: NotRequired[str]
    agent_type: NotRequired[str]
```

| Field             | Type                            | Description                                                                                                                                                                                                                     |
|:------------------|:--------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `hook_event_name` | `Literal["PostToolUseFailure"]` | Always “PostToolUseFailure”                                                                                                                                                                                                     |
| `tool_name`       | `str`                           | Name of the tool that failed                                                                                                                                                                                                    |
| `tool_input`      | `dict[str, Any]`                | Input parameters that were used                                                                                                                                                                                                 |
| `tool_use_id`     | `str`                           | Unique identifier for this tool use                                                                                                                                                                                             |
| `error`           | `str`                           | Error message from the failed execution                                                                                                                                                                                         |
| `is_interrupt`    | `bool` (optional)               | True when the failure reached Claude Code as an abort rather than as an error the tool reported. Cancelling a running tool with `interrupt()` does not fire this hook; the tool result carries the interruption message instead |
| `agent_id`        | `str` (optional)                | Subagent identifier, present when the hook fires inside a subagent                                                                                                                                                              |
| `agent_type`      | `str` (optional)                | Subagent type, present when the hook fires inside a subagent                                                                                                                                                                    |


[​](#userpromptsubmithookinput)

`UserPromptSubmitHookInput`

Input data for `UserPromptSubmit` hook events.

```python
class UserPromptSubmitHookInput(BaseHookInput):
    hook_event_name: Literal["UserPromptSubmit"]
    prompt: str
```

| Field             | Type                          | Description               |
|:------------------|:------------------------------|:--------------------------|
| `hook_event_name` | `Literal["UserPromptSubmit"]` | Always “UserPromptSubmit” |
| `prompt`          | `str`                         | The submitted prompt      |


[​](#stophookinput)

`StopHookInput`

Input data for `Stop` hook events.

```python
class StopHookInput(BaseHookInput):
    hook_event_name: Literal["Stop"]
    stop_hook_active: bool
```

| Field              | Type              | Description                     |
|:-------------------|:------------------|:--------------------------------|
| `hook_event_name`  | `Literal["Stop"]` | Always “Stop”                   |
| `stop_hook_active` | `bool`            | Whether the stop hook is active |


[​](#subagentstophookinput)

`SubagentStopHookInput`

Input data for `SubagentStop` hook events.

```python
class SubagentStopHookInput(BaseHookInput):
    hook_event_name: Literal["SubagentStop"]
    stop_hook_active: bool
    agent_id: str
    agent_transcript_path: str
    agent_type: str
```

| Field                   | Type                      | Description                            |
|:------------------------|:--------------------------|:---------------------------------------|
| `hook_event_name`       | `Literal["SubagentStop"]` | Always “SubagentStop”                  |
| `stop_hook_active`      | `bool`                    | Whether the stop hook is active        |
| `agent_id`              | `str`                     | Unique identifier for the subagent     |
| `agent_transcript_path` | `str`                     | Path to the subagent’s transcript file |
| `agent_type`            | `str`                     | Type of the subagent                   |


[​](#precompacthookinput)

`PreCompactHookInput`

Input data for `PreCompact` hook events.

```python
class PreCompactHookInput(BaseHookInput):
    hook_event_name: Literal["PreCompact"]
    trigger: Literal["manual", "auto"]
    custom_instructions: str | None
```

| Field                 | Type                        | Description                        |
|:----------------------|:----------------------------|:-----------------------------------|
| `hook_event_name`     | `Literal["PreCompact"]`     | Always “PreCompact”                |
| `trigger`             | `Literal["manual", "auto"]` | What triggered the compaction      |
| `custom_instructions` | `str | None`                | Custom instructions for compaction |


[​](#notificationhookinput)

`NotificationHookInput`

Input data for `Notification` hook events.

```python
class NotificationHookInput(BaseHookInput):
    hook_event_name: Literal["Notification"]
    message: str
    title: NotRequired[str]
    notification_type: str
```

| Field               | Type                      | Description                  |
|:--------------------|:--------------------------|:-----------------------------|
| `hook_event_name`   | `Literal["Notification"]` | Always “Notification”        |
| `message`           | `str`                     | Notification message content |
| `title`             | `str` (optional)          | Notification title           |
| `notification_type` | `str`                     | Type of notification         |


[​](#subagentstarthookinput)

`SubagentStartHookInput`

Input data for `SubagentStart` hook events.

```python
class SubagentStartHookInput(BaseHookInput):
    hook_event_name: Literal["SubagentStart"]
    agent_id: str
    agent_type: str
```

| Field             | Type                       | Description                        |
|:------------------|:---------------------------|:-----------------------------------|
| `hook_event_name` | `Literal["SubagentStart"]` | Always “SubagentStart”             |
| `agent_id`        | `str`                      | Unique identifier for the subagent |
| `agent_type`      | `str`                      | Type of the subagent               |


[​](#permissionrequesthookinput)

`PermissionRequestHookInput`

Input data for `PermissionRequest` hook events. Allows hooks to handle permission decisions programmatically.

```python
class PermissionRequestHookInput(BaseHookInput):
    hook_event_name: Literal["PermissionRequest"]
    tool_name: str
    tool_input: dict[str, Any]
    permission_suggestions: NotRequired[list[Any]]
    agent_id: NotRequired[str]
    agent_type: NotRequired[str]
```

| Field                    | Type                           | Description                                                        |
|:-------------------------|:-------------------------------|:-------------------------------------------------------------------|
| `hook_event_name`        | `Literal["PermissionRequest"]` | Always “PermissionRequest”                                         |
| `tool_name`              | `str`                          | Name of the tool requesting permission                             |
| `tool_input`             | `dict[str, Any]`               | Input parameters for the tool                                      |
| `permission_suggestions` | `list[Any]` (optional)         | Suggested permission updates from the CLI                          |
| `agent_id`               | `str` (optional)               | Subagent identifier, present when the hook fires inside a subagent |
| `agent_type`             | `str` (optional)               | Subagent type, present when the hook fires inside a subagent       |


[​](#hookjsonoutput)

`HookJSONOutput`

Union type for hook callback return values.

```python
HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput
```


[​](#synchookjsonoutput)

`SyncHookJSONOutput`

Synchronous hook output with control and decision fields.

```python
class SyncHookJSONOutput(TypedDict):
    # Control fields
    continue_: NotRequired[bool]  # Whether to proceed (default: True)
    suppressOutput: NotRequired[bool]  # Hide stdout from transcript
    stopReason: NotRequired[str]  # Message when continue is False

    # Decision fields
    decision: NotRequired[Literal["block"]]
    systemMessage: NotRequired[str]  # Warning message for user
    reason: NotRequired[str]  # Feedback for Claude

    # Hook-specific output
    hookSpecificOutput: NotRequired[HookSpecificOutput]
```

Use `continue_` (with underscore) in Python code. It is automatically converted to `continue` when sent to the CLI.


[​](#hookspecificoutput)

`HookSpecificOutput`

A discriminated union of event-specific `TypedDict` output types. The `hookEventName` field determines which fields are valid. For full details on available fields per hook event, see [Control execution with hooks](agent-sdk-hooks.md#outputs).

```python
class PreToolUseHookSpecificOutput(TypedDict):
    hookEventName: Literal["PreToolUse"]
    permissionDecision: NotRequired[Literal["allow", "deny", "ask", "defer"]]
    permissionDecisionReason: NotRequired[str]
    updatedInput: NotRequired[dict[str, Any]]
    additionalContext: NotRequired[str]


class PostToolUseHookSpecificOutput(TypedDict):
    hookEventName: Literal["PostToolUse"]
    additionalContext: NotRequired[str]
    updatedToolOutput: NotRequired[Any]
    updatedMCPToolOutput: NotRequired[Any]  # MCP tools only. Prefer updatedToolOutput, which works for all tools


class PostToolUseFailureHookSpecificOutput(TypedDict):
    hookEventName: Literal["PostToolUseFailure"]
    additionalContext: NotRequired[str]


class UserPromptSubmitHookSpecificOutput(TypedDict):
    hookEventName: Literal["UserPromptSubmit"]
    additionalContext: NotRequired[str]


class NotificationHookSpecificOutput(TypedDict):
    hookEventName: Literal["Notification"]
    additionalContext: NotRequired[str]


class SubagentStartHookSpecificOutput(TypedDict):
    hookEventName: Literal["SubagentStart"]
    additionalContext: NotRequired[str]


class PermissionRequestHookSpecificOutput(TypedDict):
    hookEventName: Literal["PermissionRequest"]
    decision: dict[str, Any]


HookSpecificOutput = (
    PreToolUseHookSpecificOutput
    | PostToolUseHookSpecificOutput
    | PostToolUseFailureHookSpecificOutput
    | UserPromptSubmitHookSpecificOutput
    | NotificationHookSpecificOutput
    | SubagentStartHookSpecificOutput
    | PermissionRequestHookSpecificOutput
)
```


[​](#asynchookjsonoutput)

`AsyncHookJSONOutput`

Async hook output that defers hook execution.

```python
class AsyncHookJSONOutput(TypedDict):
    async_: Literal[True]  # Set to True to defer execution
    asyncTimeout: NotRequired[int]  # Timeout in milliseconds
```

Use `async_` (with underscore) in Python code. It is automatically converted to `async` when sent to the CLI.


[​](#hook-usage-example)

Hook Usage Example

This example registers two hooks: one that blocks dangerous Bash commands like `rm -rf /`, and another that logs all tool usage for auditing. The security hook only runs on Bash commands (via the `matcher`), while the logging hook runs on all tools.

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext
from typing import Any


async def validate_bash_command(
    input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
) -> dict[str, Any]:
    """Validate and potentially block dangerous bash commands."""
    if input_data["tool_name"] == "Bash":
        command = input_data["tool_input"].get("command", "")
        if "rm -rf /" in command:
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Dangerous command blocked",
                }
            }
    return {}


async def log_tool_use(
    input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
) -> dict[str, Any]:
    """Log all tool usage for auditing."""
    print(f"Tool used: {input_data.get('tool_name')}")
    return {}


options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(
                matcher="Bash", hooks=[validate_bash_command], timeout=120
            ),  # 2 min for validation
            HookMatcher(
                hooks=[log_tool_use]
            ),  # Applies to all tools (per-event default timeout)
        ],
        "PostToolUse": [HookMatcher(hooks=[log_tool_use])],
    }
)

async def main():
    async for message in query(prompt="Analyze this codebase", options=options):
        print(message)


asyncio.run(main())
```


[​](#tool-input/output-types)

Tool Input/Output Types

Documentation of input/output schemas for built-in Claude Code tools. While the Python SDK doesn’t export these as types, they represent the structure of tool inputs and outputs in messages. Each output shown is the value you read from [`UserMessage.tool_use_result`](#usermessage) for that tool. Key names appear exactly as Claude Code emits them. A key annotated `| None` with a “present when” or “optional” comment is omitted when it doesn’t apply.


[​](#agent)

Agent

**Tool name:** `Agent`. The previous name `Task` is still accepted as an alias, and the `tools` list in the init [`SystemMessage`](#systemmessage) reports this tool as `Task` for backward compatibility. **Input:**

```python
{
    "description": str,  # A short (3-5 word) description of the task
    "prompt": str,  # The task for the agent to perform
    "subagent_type": str | None,  # The type of specialized agent to use
    "model": "sonnet" | "opus" | "haiku" | "fable" | None,  # Model override for this agent
    "effort": "low" | "medium" | "high" | "xhigh" | "max" | None,  # Reasoning effort for this agent
    "run_in_background": bool | None,  # Agents run in the background by default; set to False to run synchronously
    "name": str | None,  # Name for the spawned agent
    "team_name": str | None,  # Deprecated; ignored
    "mode": "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan" | None,  # Deprecated; ignored. The subagent inheritance rules decide a subagent's permission mode
    "isolation": "worktree" | "remote" | None,  # Isolation mode for the agent's changes
}
```

Launches a new agent to handle complex, multi-step tasks autonomously. **Output (status: `"completed"`):**

```python
{
    "status": "completed",
    "agentId": str,  # ID of the agent that ran
    "agentType": str | None,  # The subagent type that handled the task
    "content": [  # Result content blocks
        {
            "type": "text",
            "text": str,
            "citations": list | None,
        }
    ],
    "resolvedModel": str | None,  # Model the subagent started on
    "modelsUsed": list[str] | None,  # Models used in order, with consecutive repeats collapsed
    "totalToolUseCount": int,  # Number of tool calls the agent made
    "totalDurationMs": int,  # Execution duration in milliseconds
    "totalTokens": int,  # Token count from the final API request, not the whole run
    "usage": {  # Token usage statistics
        "input_tokens": int,
        "output_tokens": int,
        "cache_creation_input_tokens": int | None,
        "cache_read_input_tokens": int | None,
        "server_tool_use": {"web_search_requests": int, "web_fetch_requests": int} | None,
        "service_tier": str | None,
        "cache_creation": {"ephemeral_1h_input_tokens": int, "ephemeral_5m_input_tokens": int} | None,
        "inference_geo": str | None,
        "speed": str | None,
        "iterations": Any | None,
        "output_tokens_details": {"thinking_tokens": int | None} | None,
        "fallback_credit": Any | None,
    },
    "toolStats": {  # Aggregate tool activity for the run
        "readCount": int,
        "searchCount": int,
        "bashCount": int,
        "editFileCount": int,
        "linesAdded": int,
        "linesRemoved": int,
        "otherToolCount": int,
        "frameCount": int | None,
    } | None,
    "prompt": str,  # The prompt the agent ran
    "worktreePath": str | None,  # Present when Claude Code kept the subagent's worktree
    "worktreeBranch": str | None,  # Present when Claude Code created that worktree with git
}
```

**Output (status: `"async_launched"`):**

```python
{
    "status": "async_launched",
    "isAsync": bool | None,  # True on background launches
    "agentId": str,  # ID of the launched agent
    "description": str,  # The task description
    "resolvedModel": str | None,  # Model in use at the backgrounding transition
    "modelsUsed": list[str] | None,  # Models used before backgrounding, in order, with consecutive repeats collapsed
    "prompt": str,  # The prompt the agent runs
    "outputFile": str,  # File path where the agent's output is written
    "canReadOutputFile": bool | None,  # Whether the output file can be read directly
}
```

**Output (status: `"remote_launched"`):**

```python
{
    "status": "remote_launched",
    "taskId": str,  # ID of the dispatched task
    "sessionUrl": str,  # Link to the cloud session
    "description": str,  # The task description
    "prompt": str,  # The prompt the agent runs
    "outputFile": str,  # File path where the agent's output is written
}
```

Returns the result from the subagent. The output is discriminated on the `status` field: `"completed"` for finished tasks, `"async_launched"` for background tasks, and `"remote_launched"` for tasks Claude Code dispatched to a cloud session, where `sessionUrl` links to that session and `taskId` identifies it. If Claude Code [kept the subagent’s isolated worktree](../02-Claude-Code-CLI/worktrees.md#isolate-subagents-with-worktrees), `worktreePath` on the `completed` variant is where to find it, and `worktreeBranch` is its branch when Claude Code created the worktree with git. On the `completed` variant, `resolvedModel` names the model the subagent started on, which can differ from the requested `model` input when [`availableModels`](../02-Claude-Code-CLI/model-config.md#restrict-model-selection) or another override applies. This field requires Claude Code v2.1.174 or later. On the `async_launched` variant, `resolvedModel` names the model in use when the agent moved to the background, so a swap that happened before backgrounding is reflected there. The `modelsUsed` field on both variants lists the models used in order, with consecutive repeats collapsed; it’s set only when the model was swapped mid-run. `modelsUsed` and the backgrounding-time `resolvedModel` behavior require Claude Code v2.1.212 or later. Claude Code fills `usage` and `totalTokens` from the subagent’s final API request, not from the whole run. When present, `thinking_tokens` under `output_tokens_details` in `usage` is the number of that request’s output tokens that were thinking tokens. The `output_tokens_details` key requires Python SDK v0.2.136 or later, which bundles Claude Code v2.1.228. The `fallback_credit` key requires Python SDK v0.2.162 or later, which bundles Claude Code v2.1.285.


[​](#askuserquestion)

AskUserQuestion

**Tool name:** `AskUserQuestion` Asks the user clarifying questions during execution. See [Handle approvals and user input](agent-sdk-user-input.md#handle-clarifying-questions) for usage details. **Input:**

```python
{
    "questions": [  # Questions to ask the user (1-4 questions)
        {
            "question": str,  # The complete question to ask the user
            "header": str,  # Very short label displayed as a chip/tag (max 12 chars)
            "options": [  # The available choices (2-4 options)
                {
                    "label": str,  # Display text for this option (1-5 words)
                    "description": str,  # Explanation of what this option means
                    "preview": str | None,  # Preview content rendered when the option is focused
                }
            ],
            "multiSelect": bool,  # Set to true to allow multiple selections
        }
    ],
    "answers": dict[str, str] | None,
    # User answers populated by the permission system. Multi-select
    # answers are a comma-joined string of the selected labels; a
    # list of labels is accepted on input and coerced to that form
    "annotations": dict[str, dict] | None,
    # Per-question annotations from the user, keyed by question text.
    # Each value can carry "preview" (the selected option's preview
    # content) and "notes" (free-text notes on the selection)
    "metadata": dict | None,  # Analytics metadata, such as {"source": "remember"}; not displayed to the user
}
```

**Output:**

```python
{
    "questions": [  # The questions that were asked
        {
            "question": str,
            "header": str,
            "options": [{"label": str, "description": str, "preview": str | None}],
            "multiSelect": bool,
        }
    ],
    "answers": dict[str, str],  # Maps question text to answer string
    # Multi-select answers are comma-separated
    "response": str | None,
    # Freeform reply typed instead of answering the questions; when set,
    # Claude receives "The user responded: ..." in place of the answer list
    "annotations": dict[str, dict] | None,  # Per-question "preview" and "notes" from the user's selections
    "afkTimeoutMs": int | None,  # Set when the dialog auto-resolved after this many milliseconds of user inactivity; absent when the user answered
}
```


[​](#bash)

Bash

**Tool name:** `Bash` For what sets the foreground ceiling, see [Timeout and output limits](../02-Claude-Code-CLI/tools-reference.md#timeout-and-output-limits). For the background time limit, see [Time limit for background commands](../02-Claude-Code-CLI/tools-reference.md#time-limit-for-background-commands). **Input:**

```python
{
    "command": str,  # The command to execute
    "timeout": int | None,  # Milliseconds. Foreground: capped at 600000 by default, higher values are clamped. With run_in_background (Claude Code v2.1.285 or later): the background time limit, 1800000 when omitted, capped at 7200000 unless raised
    "description": str | None,  # Clear, concise description (5-10 words)
    "run_in_background": bool | None,  # Set to true to run in background
}
```

**Output:**

```python
{
    "stdout": str,  # The command's output; stdout and stderr arrive merged into this one interleaved stream
    "stderr": str,  # Notices the tool itself adds, not the command's stderr
    "interrupted": bool,  # Whether the command was interrupted
    "isImage": bool | None,  # Whether stdout contains image data
    "backgroundTaskId": str | None,  # ID of the background task if command is running in background
}
```


[​](#monitor)

Monitor

**Tool name:** `Monitor` Runs a background source and delivers each event to Claude so it can react without polling: `command` runs a script and emits one event per stdout line, and `ws` opens a WebSocket and emits one event per text frame. Provide exactly one of `command` or `ws`. When Monitor runs a command, it follows the same permission rules as Bash; a WebSocket watch prompts for approval separately. The `ws` source requires Claude Code v2.1.195 or later. See the [Monitor tool reference](../02-Claude-Code-CLI/tools-reference.md#monitor-tool) for behavior and provider availability. **Input:**

```python
{
    "command": str | None,  # Shell script; each stdout line is an event, exit ends the watch
    "ws": dict | None,  # WebSocket source: {"url": str, "protocols": list[str] | None}; each text frame is an event
    "description": str,  # Short description shown in notifications
    "timeout_ms": int | None,  # Deadline in milliseconds (default 300000, max 3600000; the effective deadline is at most 1800000)
}
```

**Output:**

```python
{
    "taskId": str,  # ID of the background monitor task
    "timeoutMs": int,  # The watch's effective deadline in milliseconds
    "persistent": bool | None,  # False: every watch has a deadline
}
```


[​](#edit)

Edit

**Tool name:** `Edit` **Input:**

```python
{
    "file_path": str,  # The absolute path to the file to modify
    "old_string": str,  # The text to replace
    "new_string": str,  # The text to replace it with
    "replace_all": bool | None,  # Replace all occurrences (default False)
}
```

**Output:**

```python
{
    "filePath": str,  # The file that was edited
    "oldString": str,  # The text that was replaced
    "newString": str,  # The text that replaced it
    "originalFile": str | None,  # File contents before the edit
    "structuredPatch": [  # Diff hunks for the change
        {
            "oldStart": int,
            "oldLines": int,
            "newStart": int,
            "newLines": int,
            "lines": list[str],
        }
    ],
    "userModified": bool,  # Whether the user changed the proposed edit before accepting it
    "replaceAll": bool,  # Whether all occurrences were replaced
    "gitDiff": {  # Optional git diff summary for the file
        "filename": str,
        "status": "modified" | "added",
        "additions": int,
        "deletions": int,
        "changes": int,
        "patch": str,
        "repository": str | None,  # GitHub owner/repo when available
    } | None,
}
```


[​](#read)

Read

**Tool name:** `Read` **Input:**

```python
{
    "file_path": str,  # The absolute path to the file to read
    "offset": int | None,  # The line number to start reading from
    "limit": int | None,  # The number of lines to read
}
```

The output takes one of the following shapes depending on what Claude read. Check the `type` key to tell them apart. **Output (type: `"text"`):**

```python
{
    "type": "text",
    "file": {
        "filePath": str,  # The file that was read
        "content": str,  # The returned content
        "numLines": int,  # Number of lines in the returned content
        "startLine": int,  # Line number the content starts at
        "totalLines": int,  # Total number of lines in the file
        "truncatedByTokenCap": bool | None,  # Present and True when a whole-file read exceeded the token cap and content is the first page
    },
}
```

**Output (type: `"image"`):**

```python
{
    "type": "image",
    "file": {
        "base64": str,  # Base64-encoded image data
        "type": "image/jpeg" | "image/png" | "image/gif" | "image/webp",  # Image MIME type
        "originalSize": int,  # Original file size in bytes
        "dimensions": {  # Optional sizing info for coordinate mapping
            "originalWidth": int | None,  # Optional; original width in pixels
            "originalHeight": int | None,  # Optional; original height in pixels
            "displayWidth": int | None,  # Optional; width after resizing
            "displayHeight": int | None,  # Optional; height after resizing
        } | None,
    },
}
```

**Output (type: `"notebook"`):**

```python
{
    "type": "notebook",
    "file": {
        "filePath": str,  # The notebook that was read
        "cells": list,  # Notebook cells
    },
}
```

**Output (type: `"pdf"`):**

```python
{
    "type": "pdf",
    "file": {
        "filePath": str,  # The PDF that was read
        "base64": str,  # Base64-encoded PDF data
        "originalSize": int,  # File size in bytes
    },
}
```

**Output (type: `"parts"`):**

```python
{
    "type": "parts",
    "file": {
        "filePath": str,  # The PDF that was read
        "originalSize": int,  # File size in bytes
        "count": int,  # Number of pages extracted as images
        "outputDir": str,  # Directory containing the extracted page images
    },
    "firstPage": int | None,  # Optional document page number of the first extracted page
}
```

**Output (type: `"file_unchanged"`):**

```python
{
    "type": "file_unchanged",  # The file is unchanged since Claude last read it in this session, so the content isn't repeated
    "file": {
        "filePath": str,
    },
    "source": "seeded" | None,  # Present when the earlier copy came from a CLAUDE.md or memory file loaded at startup rather than a Read call
}
```


[​](#write)

Write

**Tool name:** `Write` **Input:**

```python
{
    "file_path": str,  # The absolute path to the file to write
    "content": str,  # The content to write to the file
}
```

**Output:**

```python
{
    "type": "create" | "update",  # Whether the write created a new file or overwrote an existing one
    "filePath": str,  # The file that was written
    "content": str,  # The content that was written
    "structuredPatch": [  # Diff hunks; empty for a new file, when nothing changed, or when Claude Code skipped the diff
        {
            "oldStart": int,
            "oldLines": int,
            "newStart": int,
            "newLines": int,
            "lines": list[str],
        }
    ],
    "originalFile": str | None,  # Previous content; None for a new file or when the previous content was too large to include
    "gitDiff": {  # Optional git diff summary for the file
        "filename": str,
        "status": "modified" | "added",
        "additions": int,
        "deletions": int,
        "changes": int,
        "patch": str,
        "repository": str | None,  # GitHub owner/repo when available
    } | None,
    "userModified": bool | None,  # Optional; whether the user edited the proposed content before accepting it
}
```


[​](#glob)

Glob

**Tool name:** `Glob` **Input:**

```python
{
    "pattern": str,  # The glob pattern to match files against
    "path": str | None,  # The directory to search in (defaults to cwd)
}
```

**Output:**

```python
{
    "durationMs": int,  # Time taken to run the search, in milliseconds
    "numFiles": int,  # Number of paths returned, after any truncation
    "filenames": list[str],  # Matching file paths
    "truncated": bool,  # Whether the results were truncated at the 100-file limit
    "totalMatches": int | None,  # Optional total number of matching files before truncation; a lower bound when countIsComplete is False
    "countIsComplete": bool | None,  # Optional; whether totalMatches is exact
}
```

`totalMatches` and `countIsComplete` require Claude Code v2.1.191 or later.


[​](#grep)

Grep

**Tool name:** `Grep` **Input:**

```python
{
    "pattern": str,  # The regular expression pattern
    "path": str | None,  # File or directory to search in
    "glob": str | None,  # Glob pattern to filter files
    "type": str | None,  # File type to search
    "output_mode": str | None,  # "content", "files_with_matches", or "count"
    "-i": bool | None,  # Case insensitive search
    "-n": bool | None,  # Show line numbers
    "-B": int | None,  # Lines to show before each match
    "-A": int | None,  # Lines to show after each match
    "-C": int | None,  # Lines to show before and after
    "context": int | None,  # Lines to show before and after; -C is an alias
    "-o": bool | None,  # Print only the matched parts of each line
    "head_limit": int | None,  # Limit output to first N lines/entries
    "offset": int | None,  # Skip first N lines/entries before applying head_limit
    "multiline": bool | None,  # Enable multiline mode
}
```

**Output:**

```python
{
    "mode": "content" | "files_with_matches" | "count" | None,  # The output mode that was used
    "numFiles": int,  # Number of files in the result; always 0 in content mode
    "filenames": list[str],  # Matching files in files_with_matches mode; empty in the other modes
    "content": str | None,  # Matching lines in content mode, or per-file counts in count mode
    "numLines": int | None,  # Number of lines in content, present in content mode
    "numMatches": int | None,  # Total match count, present in count mode
    "totalFiles": int | None,  # Optional total before head_limit and offset, in files_with_matches mode
    "totalLines": int | None,  # Optional total before head_limit and offset, in content mode
    "appliedLimit": int | None,  # Present when head_limit truncated the result
    "appliedOffset": int | None,  # Present when an offset was applied
}
```

Grep returns this dict shape in each output mode. Which optional keys are present depends on `output_mode`. `totalFiles` requires Claude Code v2.1.208 or later. `totalLines` requires Claude Code v2.1.210 or later.


[​](#notebookedit)

NotebookEdit

**Tool name:** `NotebookEdit` **Input:**

```python
{
    "notebook_path": str,  # Absolute path to the Jupyter notebook
    "cell_id": str | None,  # The ID of the cell to edit
    "new_source": str,  # The new source for the cell
    "cell_type": "code" | "markdown" | None,  # The type of the cell
    "edit_mode": "replace" | "insert" | "delete" | None,  # Edit operation type
}
```

**Output:**

```python
{
    "new_source": str,  # The source written to the cell
    "old_source": str | None,  # Previous cell source, present for replace and delete
    "cell_id": str | None,  # ID of the edited cell, when available
    "cell_type": "code" | "markdown",  # The cell type
    "language": str,  # The notebook's programming language
    "edit_mode": str,  # The edit mode that was used
    "error": str | None,  # Error message when the operation failed
    "notebook_path": str,  # The notebook file
    "original_file": str,  # Notebook content before the edit
    "updated_file": str,  # Notebook content after the edit
}
```


[​](#webfetch)

WebFetch

**Tool name:** `WebFetch` **Input:**

```python
{
    "url": str,  # The URL to fetch content from
    "prompt": str,  # The prompt to run on the fetched content
    "offset": int | None,  # Number of characters to skip from the start of the page. Requires Python Agent SDK 0.2.164 or later
}
```

**Output:**

```python
{
    "bytes": int,  # Size of the fetched content in bytes
    "code": int,  # HTTP response code
    "codeText": str,  # HTTP response code text
    "result": str,  # Processed result from applying the prompt to the content
    "durationMs": int,  # Time to fetch and process the content, in milliseconds
    "url": str,  # URL that was fetched
}
```


[​](#websearch)

WebSearch

**Tool name:** `WebSearch` **Input:**

```python
{
    "query": str,  # The search query to use
    "allowed_domains": list[str] | None,  # Only include results from these domains
    "blocked_domains": list[str] | None,  # Never include results from these domains
}
```

**Output:**

```python
{
    "query": str,  # The search query
    "results": list[str | {"tool_use_id": str, "content": list[{"title": str, "url": str}]}],
    "durationSeconds": float,  # Search duration in seconds
}
```


[​](#todowrite)

TodoWrite

**Tool name:** `TodoWrite`

The following tools are available by default only on Claude 3.x models, Opus 4 through 4.7, Sonnet 4 through 4.6, and Haiku 4.5. On every other model, including model IDs Claude Code doesn’t recognize, they aren’t available unless you opt in:

- `TodoWrite`
- `TaskCreate`
- `TaskGet`
- `TaskUpdate`
- `TaskList`

Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.See [Model availability](agent-sdk-todo-tracking.md#model-availability) to opt in.

**Input:**

```python
{
    "todos": [
        {
            "content": str,  # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str,  # Active form of the description
        }
    ]
}
```

**Output:**

```python
{
    "oldTodos": [  # The todo list before the update
        {
            "content": str,
            "status": "pending" | "in_progress" | "completed",
            "activeForm": str,
        }
    ],
    "newTodos": [  # The todo list after the update
        {
            "content": str,
            "status": "pending" | "in_progress" | "completed",
            "activeForm": str,
        }
    ],
}
```


[​](#taskcreate)

TaskCreate

**Tool name:** `TaskCreate` **Input:**

```python
{
    "subject": str,  # Short task title
    "description": str,  # Detailed task body
    "activeForm": str | None,  # Present-tense label shown while in progress
    "metadata": dict | None,  # Arbitrary caller metadata
}
```

**Output:**

```python
{
    "task": {"id": str, "subject": str},  # Created task with assigned ID
}
```


[​](#taskupdate)

TaskUpdate

**Tool name:** `TaskUpdate` **Input:**

```python
{
    "taskId": str,  # ID of the task to patch
    "status": Literal["pending", "in_progress", "completed", "deleted"] | None,
    "subject": str | None,
    "description": str | None,
    "activeForm": str | None,
    "addBlocks": list[str] | None,  # Task IDs this task now blocks
    "addBlockedBy": list[str] | None,  # Task IDs that now block this task
    "owner": str | None,
    "metadata": dict | None,
}
```

**Output:**

```python
{
    "success": bool,
    "taskId": str,
    "updatedFields": list[str],  # Names of fields that changed
    "error": str | None,
    "statusChange": {"from": str, "to": str} | None,
}
```


[​](#taskget)

TaskGet

**Tool name:** `TaskGet` **Input:**

```python
{
    "taskId": str,  # ID of the task to read
}
```

**Output:**

```python
{
    "task": {
        "id": str,
        "subject": str,
        "description": str,
        "status": Literal["pending", "in_progress", "completed"],
        "blocks": list[str],
        "blockedBy": list[str],
    } | None,  # None when the ID is not found
}
```


[​](#tasklist)

TaskList

**Tool name:** `TaskList` **Input:**

```python
{}
```

**Output:**

```python
{
    "tasks": [
        {
            "id": str,
            "subject": str,
            "status": Literal["pending", "in_progress", "completed"],
            "owner": str | None,
            "blockedBy": list[str],
        }
    ],
}
```


[​](#taskoutput)

TaskOutput

Removed in Claude Code v2.1.277. Previously retrieved output from a running or completed background task, with `BashOutput` accepted as an alias; Claude reads a background task’s output file with `Read` instead. A `disallowed_tools` entry or a deny rule that still names either name is ignored without a warning.


[​](#taskstop)

TaskStop

**Tool name:** `TaskStop`. The previous names `KillShell` and `KillBash` are still accepted as aliases. **Input:**

```python
{
    "task_id": str | None,  # The ID of the background task to stop
    "shell_id": str | None,  # Deprecated: use task_id instead
}
```

**Output:**

```python
{
    "message": str,  # Status message about the operation
    "task_id": str,  # The ID of the task that was stopped
    "task_type": str,  # The type of the task that was stopped
    "command": str | None,  # The command or description of the stopped task
}
```


[​](#exitplanmode)

ExitPlanMode

**Tool name:** `ExitPlanMode` **Input:**

```python
{
    "plan": str  # The plan to run by the user for approval
}
```

**Output:**

```python
{
    "plan": str | None,  # The plan that was presented to the user
    "isAgent": bool,  # True when a subagent called the tool
    "filePath": str | None,  # Present when the plan was saved to a file
    "hasTaskTool": bool | None,  # Optional; whether the Agent tool is available in the current context
    "planWasEdited": bool | None,  # Present and True when the user edited the plan before approving
    "awaitingLeaderApproval": bool | None,  # Present and True when a teammate sent the plan to the team lead for approval
    "requestId": str | None,  # Optional ID of that approval request
}
```


[​](#listmcpresources)

ListMcpResources

**Tool name:** `ListMcpResourcesTool` **Input:**

```python
{
    "server": str | None  # Optional server name to filter resources by
}
```

The result is a list rather than a dict, so `tool_use_result` holds a `list` for this tool. **Output:**

```python
[  # One entry per resource
    {
        "uri": str,  # Resource URI
        "name": str,  # Resource name
        "mimeType": str | None,  # Optional MIME type
        "description": str | None,  # Optional description
        "server": str,  # Server that provides this resource
    }
]
```


[​](#readmcpresource)

ReadMcpResource

**Tool name:** `ReadMcpResourceTool` **Input:**

```python
{
    "server": str,  # The MCP server name
    "uri": str,  # The resource URI to read
}
```

**Output:**

```python
{
    "contents": [
        {
            "uri": str,  # Resource URI
            "mimeType": str | None,  # Optional MIME type
            "text": str | None,  # Text content, or a note about the binary content
            "blobSavedTo": str | None,  # Present when Claude Code saved binary content to disk; path of the saved file
        }
    ],
    "error": str | None,  # Present when the server couldn't read the resource
}
```


[​](#build-a-continuous-conversation-interface)

Build a continuous conversation interface

The following example keeps one `ClaudeSDKClient` connected across turns, so Claude remembers earlier messages. Type `new` to disconnect and reconnect for a fresh session, or `exit` to end the conversation.

```python
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    TextBlock,
)
import asyncio


class ConversationSession:
    """Maintains a single conversation session with Claude."""

    def __init__(self, options: ClaudeAgentOptions | None = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0

    async def start(self):
        await self.client.connect()
        print("Starting conversation session. Claude will remember context.")
        print(
            "Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session"
        )

        while True:
            user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")

            if user_input.lower() == "exit":
                break
            elif user_input.lower() == "interrupt":
                await self.client.interrupt()
                print("Task interrupted!")
                continue
            elif user_input.lower() == "new":
                # Disconnect and reconnect for a fresh session
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Started new conversation session (previous context cleared)")
                continue

            # Send message - the session retains all previous messages
            await self.client.query(user_input)
            self.turn_count += 1

            # Process response
            print(f"[Turn {self.turn_count}] Claude: ", end="")
            async for message in self.client.receive_response():
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, TextBlock):
                            print(block.text, end="")
            print()  # New line after response

        await self.client.disconnect()
        print(f"Conversation ended after {self.turn_count} turns.")


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"], permission_mode="acceptEdits"
    )
    session = ConversationSession(options)
    await session.start()


# Example conversation:
# Turn 1 - You: "Create a file called hello.py"
# Turn 1 - Claude: "I'll create a hello.py file for you..."
# Turn 2 - You: "What's in that file?"
# Turn 2 - Claude: "The hello.py file I just created contains..." (remembers!)
# Turn 3 - You: "Add a main function to it"
# Turn 3 - Claude: "I'll add a main function to hello.py..." (knows which file!)

asyncio.run(main())
```


[​](#error-handling)

Error handling

The following example wraps a `query()` call in handlers for four of the [error types](#error-types) the SDK raises. This example catches [`ResultError`](#resulterror), which requires Python Agent SDK 0.2.140 or later.

```python
import asyncio

from claude_agent_sdk import (
    query,
    CLINotFoundError,
    ProcessError,
    ResultError,
    CLIJSONDecodeError,
)


async def main():
    try:
        async for message in query(prompt="Hello"):
            print(message)
    except CLINotFoundError:
        print(
            "Claude Code CLI not found. Try reinstalling: pip install --force-reinstall claude-agent-sdk"
        )
    # Catch ResultError before ProcessError, which it subclasses. Its message
    # carries the error text. A failed final request, such as an API error,
    # arrives with subtype "success", so branch on terminal_reason first.
    except ResultError as e:
        if e.terminal_reason == "api_error":
            print(f"API request failed: {e}")
        else:
            print(f"Query ended with an error result ({e.terminal_reason or e.subtype}): {e}")
    except ProcessError as e:
        print(f"Process failed with exit code: {e.exit_code}")
    except CLIJSONDecodeError as e:
        print(f"Failed to parse response: {e}")


asyncio.run(main())
```


[​](#sandbox-configuration)

Sandbox Configuration


[​](#sandboxsettings)

`SandboxSettings`

Configuration for sandbox behavior. Use this to enable command sandboxing and configure network restrictions programmatically.

```python
class SandboxSettings(TypedDict, total=False):
    enabled: bool
    autoAllowBashIfSandboxed: bool
    excludedCommands: list[str]
    allowUnsandboxedCommands: bool
    network: SandboxNetworkConfig
    ignoreViolations: SandboxIgnoreViolations
    enableWeakerNestedSandbox: bool
```

| Property                    | Type                                                  | Default | Description                                                                                                                                                                                                                                       |
|:----------------------------|:------------------------------------------------------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `enabled`                   | `bool`                                                | `False` | Enable sandbox mode for command execution                                                                                                                                                                                                         |
| `autoAllowBashIfSandboxed`  | `bool`                                                | `True`  | Auto-approve bash commands when sandbox is enabled                                                                                                                                                                                                |
| `excludedCommands`          | `list[str]`                                           | `[]`    | Commands that bypass sandbox restrictions, such as `["docker *"]`. These run unsandboxed automatically without model involvement; [`sandbox.excludedCommands`](../02-Claude-Code-CLI/settings-reference.md#sandbox-excludedcommands) covers when an entry applies |
| `allowUnsandboxedCommands`  | `bool`                                                | `True`  | Allow the model to request running commands outside the sandbox. When `True`, the model can set `dangerouslyDisableSandbox` in tool input, which falls back to the [permissions system](#permissions-fallback-for-unsandboxed-commands)           |
| `network`                   | [`SandboxNetworkConfig`](#sandboxnetworkconfig)       | `None`  | Network-specific sandbox configuration                                                                                                                                                                                                            |
| `ignoreViolations`          | [`SandboxIgnoreViolations`](#sandboxignoreviolations) | `None`  | Configure which sandbox violations to ignore                                                                                                                                                                                                      |
| `enableWeakerNestedSandbox` | `bool`                                                | `False` | Enable a weaker nested sandbox for compatibility                                                                                                                                                                                                  |

The sandbox depends on platform support and, on Linux, tools like `bubblewrap` and `socat`. By default, when `enabled` is `True` but the sandbox can’t start, commands run unsandboxed with a warning on stderr. This default differs from the TypeScript SDK, where `failIfUnavailable` defaults to `true`.Set `"failIfUnavailable": True` in your sandbox settings to stop instead. The key isn’t declared on `SandboxSettings` yet, but the SDK forwards it to Claude Code, which honors it. `query()` then reports a `ResultMessage` with `subtype="error_during_execution"` and the reason in `errors`. Because this is a single-shot `query()` call, the SDK raises after yielding that error result, so wrap the loop in a try block to continue past it. See [Handle the result](agent-sdk-agent-loop.md#handle-the-result) for the error contract.


[​](#example-usage)

Example usage

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

sandbox_settings = {
    "enabled": True,
    "autoAllowBashIfSandboxed": True,
    "failIfUnavailable": True,
    "network": {"allowLocalBinding": True},
}


async def main():
    try:
        async for message in query(
            prompt="Build and test my project",
            options=ClaudeAgentOptions(sandbox=sandbox_settings),
        ):
            print(message)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # such as when failIfUnavailable is set and the sandbox can't start.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

**Unix socket security**: The `allowUnixSockets` option can grant access to system services that reach outside the sandbox. For example, allowing `/var/run/docker.sock` effectively grants full host system access through the Docker API, bypassing sandbox isolation. Only allow Unix sockets that are strictly necessary and understand the security implications of each.


[​](#sandboxnetworkconfig)

`SandboxNetworkConfig`

Network-specific configuration for sandbox mode. These settings apply to sandboxed Bash commands when `enabled` is `True` in the parent [`SandboxSettings`](#sandboxsettings). They do not restrict the WebFetch tool, which uses [permission rules](../02-Claude-Code-CLI/permissions.md#webfetch) instead.

```python
class SandboxNetworkConfig(TypedDict, total=False):
    allowedDomains: list[str]
    deniedDomains: list[str]
    allowManagedDomainsOnly: bool
    allowUnixSockets: list[str]
    allowAllUnixSockets: bool
    allowLocalBinding: bool
    allowMachLookup: list[str]
    httpProxyPort: int
    socksProxyPort: int
```

| Property                  | Type        | Default | Description                                                                                                                                                                                   |
|:--------------------------|:------------|:--------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allowedDomains`          | `list[str]` | `[]`    | Domain names that sandboxed processes can access                                                                                                                                              |
| `deniedDomains`           | `list[str]` | `[]`    | Domain names that sandboxed processes cannot access. Takes precedence over `allowedDomains`                                                                                                   |
| `allowManagedDomainsOnly` | `bool`      | `False` | Managed-settings only: when set in managed settings, ignore `allowedDomains` and `WebFetch(domain:...)` allow rules from non-managed settings sources. Has no effect when set via SDK options |
| `allowUnixSockets`        | `list[str]` | `[]`    | macOS only: Unix socket paths that processes can access, such as the Docker socket. Ignored on Linux                                                                                          |
| `allowAllUnixSockets`     | `bool`      | `False` | Allow access to all Unix sockets                                                                                                                                                              |
| `allowLocalBinding`       | `bool`      | `False` | Allow processes to bind to local ports (for example, for dev servers)                                                                                                                         |
| `allowMachLookup`         | `list[str]` | `[]`    | macOS only: XPC/Mach service names to allow. Supports a trailing wildcard                                                                                                                     |
| `httpProxyPort`           | `int`       | `None`  | HTTP proxy port for network requests                                                                                                                                                          |
| `socksProxyPort`          | `int`       | `None`  | SOCKS proxy port for network requests                                                                                                                                                         |

The built-in sandbox proxy enforces the network allowlist based on the requested hostname and does not terminate or inspect TLS traffic, so techniques such as [domain fronting](https://en.wikipedia.org/wiki/Domain_fronting) can potentially bypass it. See [Sandboxing security limitations](../02-Claude-Code-CLI/sandboxing.md#security-limitations) for details and [Secure deployment](agent-sdk-secure-deployment.md#traffic-forwarding) for configuring a TLS-terminating proxy.


[​](#sandboxignoreviolations)

`SandboxIgnoreViolations`

Configuration for ignoring specific sandbox violations.

```python
class SandboxIgnoreViolations(TypedDict, total=False):
    file: list[str]
    network: list[str]
```

| Property  | Type        | Default | Description                                 |
|:----------|:------------|:--------|:--------------------------------------------|
| `file`    | `list[str]` | `[]`    | File path patterns to ignore violations for |
| `network` | `list[str]` | `[]`    | Network patterns to ignore violations for   |


[​](#permissions-fallback-for-unsandboxed-commands)

Permissions Fallback for Unsandboxed Commands

When `allowUnsandboxedCommands` is enabled, the model can request to run commands outside the sandbox by setting `dangerouslyDisableSandbox: True` in the tool input. These requests fall back to the existing permissions system, meaning your `can_use_tool` handler is invoked, allowing you to implement custom authorization logic. Your `excludedCommands` entries instead take a call out of the sandbox with no model involvement; [`sandbox.excludedCommands`](../02-Claude-Code-CLI/settings-reference.md#sandbox-excludedcommands) covers when an entry applies. The following example logs each unsandboxed request and denies it unless your own authorization logic allows it:

```python
import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    HookMatcher,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


def is_command_authorized(command: str | None) -> bool:
    # Replace with your own authorization logic
    return False


async def can_use_tool(
    tool: str, input: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    # Check if the model is requesting to bypass the sandbox
    if tool == "Bash" and input.get("dangerouslyDisableSandbox"):
        # The model is requesting to run this command outside the sandbox
        print(f"Unsandboxed command requested: {input.get('command')}")

        if is_command_authorized(input.get("command")):
            return PermissionResultAllow()
        return PermissionResultDeny(
            message="Command not authorized for unsandboxed execution"
        )
    return PermissionResultAllow()


# Required: dummy hook keeps the stream open for can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}


async def prompt_stream():
    yield {
        "type": "user",
        "message": {"role": "user", "content": "Deploy my application"},
    }


async def main():
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            sandbox={
                "enabled": True,
                "allowUnsandboxedCommands": True,  # Model can request unsandboxed execution
            },
            permission_mode="default",
            can_use_tool=can_use_tool,
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
        ),
    ):
        print(message)


asyncio.run(main())
```

Commands running with `dangerouslyDisableSandbox: True` have full system access. Ensure your `can_use_tool` handler validates these requests carefully.If `permission_mode` is set to `bypassPermissions` and `allowUnsandboxedCommands` is enabled, the model can autonomously execute commands outside the sandbox without approval prompts, apart from the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves). Review enterprise hardening guidance before enabling this combination.


[​](#see-also)

See also

- [SDK overview](agent-sdk-overview.md) - General SDK concepts
- [TypeScript SDK reference](agent-sdk-typescript.md) - TypeScript SDK documentation
- [Custom tools](agent-sdk-custom-tools.md) - Define in-process MCP tools for Claude to call
- [CLI reference](../02-Claude-Code-CLI/cli-reference.md) - Command-line interface
- [Common workflows](../02-Claude-Code-CLI/common-workflows.md) - Step-by-step guides

---

## Quickstart - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/quickstart
Path: 05-Agent-SDK/agent-sdk-quickstart.md

## On this page

- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [Create a buggy file](#create-a-buggy-file)
- [Build an agent that finds and fixes bugs](#build-an-agent-that-finds-and-fixes-bugs)
  - [Run your agent](#run-your-agent)
  - [Try other prompts](#try-other-prompts)
  - [Customize your agent](#customize-your-agent)
- [Key concepts](#key-concepts)
- [Next steps](#next-steps)

Agent SDK

# Quickstart

Copy pageCopy page

Get started with the Python or TypeScript Agent SDK to build AI agents that work autonomously

Copy pageCopy page

Use the Agent SDK to build an AI agent that reads your code, finds bugs, and fixes them, all without manual intervention. **What you’ll do:**

1.  Set up a project with the Agent SDK
2.  Create a file with some buggy code
3.  Run an agent that finds and fixes the bugs automatically


[​](#prerequisites)

Prerequisites

- **Node.js 18+** or **Python 3.10+**
- An **Anthropic account**. If you don’t have one, [sign up here](../04-API-Reference/Other/usage-limits.md).


[​](#setup)

Setup

1

Create a project folder

Create a new directory for this quickstart:

```python
mkdir my-agent
cd my-agent
```

For your own projects, you can run the SDK from any folder; it will have access to files in that directory and its subdirectories by default.

2

Install the SDK

Install the Agent SDK package for your language:

- TypeScript (new project)

- TypeScript (existing project)

- Python (uv)

- Python (pip)

```python
npm init -y
npm pkg set type=module
npm install @anthropic-ai/claude-agent-sdk
npm install --save-dev tsx
```

Setting `"type": "module"` in `package.json` lets your agent script use top-level `await`, and [tsx](https://tsx.hirok.io) runs TypeScript files directly. npm prints `added N packages` when the install succeeds.

```python
npm install @anthropic-ai/claude-agent-sdk
npm install --save-dev tsx
```

[tsx](https://tsx.hirok.io) runs TypeScript files directly. If your project uses CommonJS, name your agent script `agent.mts` instead of `agent.ts`. The `.mts` extension makes tsx treat the file as an ES module, so top-level `await` works without converting your whole project to ES modules. Use `agent.mts` in place of `agent.ts` in the create and run steps later in this quickstart.

[Install uv](https://docs.astral.sh/uv/), a fast Python package manager that handles virtual environments automatically. Then initialize a project and add the SDK:

```python
uv init
uv add claude-agent-sdk
```

Create and activate a virtual environment, then install the package.On macOS or Linux:

```python
python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk
```

On Windows:

```python
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install claude-agent-sdk
```

If PowerShell blocks `Activate.ps1` with an execution policy error, run `Set-ExecutionPolicy -Scope Process RemoteSigned` first.

Both the TypeScript and Python SDKs bundle a native Claude Code binary, so most installs need no separate Claude Code install. Some installs have no bundled binary:

- If pip installs the Python SDK’s source distribution instead of a platform wheel, for example on ARM64 Windows, no binary is bundled. [Install Claude Code natively](../01-Getting-Started/setup.md#install-claude-code). The Python SDK finds it on your `PATH`.
- The TypeScript SDK installs its binary through npm optional dependencies, so an install that skips them, for example `npm ci --omit=optional`, gets no binary even on a supported platform. Reinstall without skipping optional dependencies, or [install Claude Code natively](../01-Getting-Started/setup.md#install-claude-code) and set `pathToClaudeCodeExecutable` to its path.

3

Set your API key

Get an API key from the [Claude Console](../04-API-Reference/Other/usage-limits.md), then set it as an environment variable in the shell where you’ll run your agent:

- macOS / Linux

- Windows (PowerShell)

```python
export ANTHROPIC_API_KEY=your-api-key
```

```python
$env:ANTHROPIC_API_KEY = "your-api-key"
```

The SDK reads the key from the environment of the process that runs your agent; it doesn’t load `.env` files automatically. If you keep the key in a `.env` file, load it yourself, for example with the `dotenv` package, before calling the SDK.The SDK also supports authentication via third-party API providers:

- **Amazon Bedrock**: set `CLAUDE_CODE_USE_BEDROCK=1` environment variable and configure AWS credentials
- **Claude Platform on AWS**: set `CLAUDE_CODE_USE_ANTHROPIC_AWS=1` and `ANTHROPIC_AWS_WORKSPACE_ID`, then configure AWS credentials
- **Google Cloud’s Agent Platform**: set `CLAUDE_CODE_USE_VERTEX=1` environment variable and configure Google Cloud credentials
- **Microsoft Foundry**: set `CLAUDE_CODE_USE_FOUNDRY=1` environment variable and configure Azure credentials

See the setup guides for [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md), or [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md) for details.

Unless previously approved, Anthropic does not allow third party developers to offer claude.ai login or rate limits for their products, including agents built on the Claude Agent SDK. Use the API key authentication methods described in this document instead.


[​](#create-a-buggy-file)

Create a buggy file

This quickstart walks you through building an agent that can find and fix bugs in code. First, you need a file with some intentional bugs for the agent to fix. Create `utils.py` in the `my-agent` directory and paste the following code:

```python
def calculate_average(numbers):
    total = 0
    for num in numbers:
        total += num
    return total / len(numbers)


def get_user_name(user):
    return user["name"].upper()
```

This code has two bugs:

1.  `calculate_average([])` crashes with division by zero
2.  `get_user_name(None)` crashes with a TypeError


[​](#build-an-agent-that-finds-and-fixes-bugs)

Build an agent that finds and fixes bugs

Create `agent.py` if you’re using the Python SDK, or `agent.ts` for TypeScript. Use `agent.mts` instead if your existing project uses CommonJS:

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage


async def main():
    # Agentic loop: streams messages as Claude works
    async for message in query(
        prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Edit", "Glob"],  # Auto-approve these tools
            permission_mode="acceptEdits",  # Auto-approve file edits
        ),
    ):
        # Print human-readable output
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "text"):
                    print(block.text)  # Claude's reasoning
                elif hasattr(block, "name"):
                    print(f"Tool: {block.name}")  # Tool being called
        elif isinstance(message, ResultMessage):
            print(f"Done: {message.subtype}")  # Final result


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Agentic loop: streams messages as Claude works
for await (const message of query({
  prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
  options: {
    allowedTools: ["Read", "Edit", "Glob"], // Auto-approve these tools
    permissionMode: "acceptEdits" // Auto-approve file edits
  }
})) {
  // Print human-readable output
  if (message.type === "assistant" && message.message?.content) {
    for (const block of message.message.content) {
      if ("text" in block) {
        console.log(block.text); // Claude's reasoning
      } else if ("name" in block) {
        console.log(`Tool: ${block.name}`); // Tool being called
      }
    }
  } else if (message.type === "result") {
    console.log(`Done: ${message.subtype}`); // Final result
  }
}
```

This code has three main parts:

1.  **`query`**: the main entry point that creates the agentic loop. It returns an async iterator, so you use `async for` to stream messages as Claude works. See the full API in the [Python](agent-sdk-python.md#query) or [TypeScript](agent-sdk-typescript.md#query) SDK reference.
2.  **`prompt`**: what you want Claude to do. Claude figures out which tools to use based on the task.
3.  **`options`**: configuration for the agent. This example uses `allowedTools` to pre-approve `Read`, `Edit`, and `Glob`, and `permissionMode: "acceptEdits"` to auto-approve file changes. Other options include `systemPrompt`, `mcpServers`, and more. See all options for [Python](agent-sdk-python.md#claudeagentoptions) or [TypeScript](agent-sdk-typescript.md#options).

The `async for` loop keeps running as Claude thinks, calls tools, observes results, and decides what to do next. Each iteration yields a message: Claude’s reasoning, a tool call, a tool result, or the final outcome. The SDK handles the orchestration, tool execution, context management, and retries, so you consume the stream. The loop ends when Claude finishes the task or hits an error. The message handling inside the loop filters for human-readable output. Without filtering, you’d see raw message objects including system initialization and internal state, which is useful for debugging but noisy otherwise.

This example uses streaming to show progress in real-time. If you don’t need live output (for example, for background jobs or CI pipelines), you can collect all messages at once. See [Streaming vs. single-turn mode](agent-sdk-streaming-vs-single-mode.md) for details.


[​](#run-your-agent)

Run your agent

Your agent is ready. Run it with the following command:

- TypeScript

- Python (uv)

- Python (pip)

```python
npx tsx agent.ts
```

If you named your script `agent.mts`, run `npx tsx agent.mts` instead.

```python
uv run agent.py
```

With your virtual environment still activated:

```python
python agent.py
```

As it works, the agent prints its reasoning and each tool it calls, ending with `Done: success`. After running, check `utils.py`. You’ll see defensive code handling empty lists and null users. Your agent autonomously:

1.  **Read** `utils.py` to understand the code
2.  **Analyzed** the logic and identified edge cases that would crash
3.  **Edited** the file to add proper error handling

This is what makes the Agent SDK different: Claude executes tools directly instead of asking you to implement them.

If you see an authentication error such as `Not logged in` or `Invalid API key`, make sure you’ve set the `ANTHROPIC_API_KEY` environment variable in the shell where you run your agent. The SDK doesn’t load `.env` files automatically.For the causes and fixes behind these and other authentication errors, see [Authentication errors](../02-Claude-Code-CLI/errors.md#authentication-errors) in the Error reference.


[​](#try-other-prompts)

Try other prompts

Now that your agent is set up, try some different prompts:

- `"Add docstrings to all functions in utils.py"`
- `"Add type hints to all functions in utils.py"`
- `"Create a README.md documenting the functions in utils.py"`


[​](#customize-your-agent)

Customize your agent

You can modify your agent’s behavior by changing the options. Here are a few examples: **Add web search capability:**

Python

TypeScript

```python
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Edit", "Glob", "WebSearch"], permission_mode="acceptEdits"
)
```

```python
const _ = {
  options: {
    allowedTools: ["Read", "Edit", "Glob", "WebSearch"],
    permissionMode: "acceptEdits"
  }
};
```

**Give Claude a custom system prompt:**

Python

TypeScript

```python
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Edit", "Glob"],
    permission_mode="acceptEdits",
    system_prompt="You are a senior Python developer. Always follow PEP 8 style guidelines.",
)
```

```python
const _ = {
  options: {
    allowedTools: ["Read", "Edit", "Glob"],
    permissionMode: "acceptEdits",
    systemPrompt: "You are a senior Python developer. Always follow PEP 8 style guidelines."
  }
};
```

**Run commands in the terminal:**

Python

TypeScript

```python
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Edit", "Glob", "Bash"], permission_mode="acceptEdits"
)
```

```python
const _ = {
  options: {
    allowedTools: ["Read", "Edit", "Glob", "Bash"],
    permissionMode: "acceptEdits"
  }
};
```

With `Bash` enabled, try: `"Write unit tests for utils.py, run them, and fix any failures"` Each of these snippets sets fields on the same options object. For more information, see [Configure your agent](agent-sdk-configuration.md).


[​](#key-concepts)

Key concepts

**Tools** control what your agent can do:

| Tools                                  | What the agent can do   |
|----------------------------------------|-------------------------|
| `Read`, `Glob`, `Grep`                 | Read-only analysis      |
| `Read`, `Edit`, `Glob`                 | Analyze and modify code |
| `Read`, `Edit`, `Bash`, `Glob`, `Grep` | Full automation         |

**Permission modes** control how much human oversight you want. The SDK evaluates the active mode together with your allow and deny rules in a fixed order, described in [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated). For the full list of modes, their behavior, and when to use each, see [Permission mode in How the agent loop works](agent-sdk-agent-loop.md#permission-mode).


[​](#next-steps)

Next steps

Now that you’ve created your first agent, learn how to extend its capabilities and tailor it to your use case:

- **[Configure your agent](agent-sdk-configuration.md)**: compose the options object and find the page that covers each setting
- **[Permissions](agent-sdk-permissions.md)**: control what your agent can do and when it needs approval
- **[Hooks](agent-sdk-hooks.md)**: run custom code before or after tool calls
- **[Sessions](agent-sdk-sessions.md)**: build multi-turn agents that maintain context
- **[MCP servers](agent-sdk-mcp.md)**: connect to databases, browsers, APIs, and other external systems
- **[Hosting](agent-sdk-hosting.md)**: deploy agents to Docker, cloud, and CI/CD
- **[Example agents](https://github.com/anthropics/claude-agent-sdk-demos)**: see complete examples: email assistant, research agent, and more
- **[Troubleshooting](agent-sdk-troubleshooting.md)**: fix errors when the CLI fails to start or exits, or a result arrives without structured output

---

## Securely deploying AI agents - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/secure-deployment
Path: 05-Agent-SDK/agent-sdk-secure-deployment.md

## On this page

- [Threat model](#threat-model)
- [Built-in security features](#built-in-security-features)
- [Security principles](#security-principles)
  - [Security boundaries](#security-boundaries)
  - [Least privilege](#least-privilege)
  - [Defense in depth](#defense-in-depth)
- [Isolation technologies](#isolation-technologies)
  - [Sandbox runtime](#sandbox-runtime)
  - [Containers](#containers)
  - [gVisor](#gvisor)
  - [Virtual machines](#virtual-machines)
  - [Cloud deployments](#cloud-deployments)
- [Credential management](#credential-management)
  - [The proxy pattern](#the-proxy-pattern)
  - [Configuring Claude Code to use a proxy](#configuring-claude-code-to-use-a-proxy)
  - [Implementing a proxy](#implementing-a-proxy)
  - [Credentials for other services](#credentials-for-other-services)
  - [Custom tools](#custom-tools)
  - [Traffic forwarding](#traffic-forwarding)
- [Filesystem configuration](#filesystem-configuration)
  - [Read-only code mounting](#read-only-code-mounting)
  - [Writable locations](#writable-locations)
- [Further reading](#further-reading)

Deployment

# Securely deploying AI agents

Copy pageCopy page

A guide to securing Claude Code and Agent SDK deployments with isolation, credential management, and network controls

Copy pageCopy page

Claude Code and the Agent SDK can execute code, access files, and interact with external services on your behalf. Unlike traditional software that follows predetermined code paths, these tools generate their actions dynamically based on context and goals. This flexibility is what makes them useful, but it also means their behavior can be influenced by the content they process: files, webpages, or user input. This is sometimes called prompt injection. For example, if a repository’s README contains unusual instructions, Claude Code might incorporate those into its actions in ways the operator didn’t anticipate. This guide covers practical ways to reduce this risk. Not every deployment needs maximum security. A developer running Claude Code on their laptop has different requirements than a company processing customer data in a multi-tenant environment. This guide presents options ranging from Claude Code’s built-in security features to hardened production architectures, so you can choose what fits your situation.


[​](#threat-model)

Threat model

Agents can take unintended actions due to prompt injection (instructions embedded in content they process) or model error. Claude models are designed to resist this; see the [model overview](../20-Models/about-claude-models-overview.md) and the system card for the model you deploy for evaluation details. Defense in depth is still good practice though. For example, if an agent processes a malicious file that instructs it to send customer data to an external server, network controls can block that request entirely.


[​](#built-in-security-features)

Built-in security features

Claude Code includes several security features that address common concerns. See the [security documentation](../13-Enterprise-Admin/security.md) for full details.

- **Permissions system**: Every tool and bash command can be configured to allow, block, or prompt the user for approval. Use glob patterns to create rules like “allow all npm commands” or “block any command with sudo”. Organizations can set policies that apply across all users. See [permissions](../02-Claude-Code-CLI/permissions.md).
- **Command parsing for permissions**: Before executing bash commands, Claude Code parses them into an AST and matches the result against your permission rules. Commands that cannot be parsed cleanly, or that do not match an allow rule, require explicit approval. A small set of constructs such as `eval` always require approval regardless of allow rules. This is a permission gate, not a sandbox; apart from built-in safety checks such as the [critical-path check](../02-Claude-Code-CLI/permission-modes.md#critical-paths) on `rm` and `rmdir` and the [protected paths](../02-Claude-Code-CLI/permission-modes.md#protected-paths) list, it does not infer whether a command is dangerous from its target path or effects.
- **Web search summarization**: Search results are summarized rather than passing raw content directly into the context, reducing the risk of prompt injection from malicious web content.
- **Sandbox mode**: Bash commands can run in a sandboxed environment that restricts filesystem and network access. See the [sandboxing documentation](../02-Claude-Code-CLI/sandboxing.md) for details.


[​](#security-principles)

Security principles

For deployments that require additional hardening beyond Claude Code’s defaults, these principles guide the available options.


[​](#security-boundaries)

Security boundaries

A security boundary separates components with different trust levels. For high-security deployments, you can place sensitive resources (like credentials) outside the boundary containing the agent. If something goes wrong in the agent’s environment, resources outside that boundary remain protected. For example, rather than giving an agent direct access to an API key, you could run a proxy outside the agent’s environment that injects the key into requests. The agent can make API calls, but it never sees the credential itself. This pattern is useful for multi-tenant deployments or when processing untrusted content.


[​](#least-privilege)

Least privilege

When needed, you can restrict the agent to only the capabilities required for its specific task:

| Resource            | Restriction options                             |
|---------------------|-------------------------------------------------|
| Filesystem          | Mount only needed directories, prefer read-only |
| Network             | Restrict to specific endpoints via proxy        |
| Credentials         | Inject via proxy rather than exposing directly  |
| System capabilities | Drop Linux capabilities in containers           |


[​](#defense-in-depth)

Defense in depth

For high-security environments, layering multiple controls provides additional protection. Options include:

- Container isolation
- Network restrictions
- Filesystem controls
- Request validation at a proxy

The right combination depends on your threat model and operational requirements.


[​](#isolation-technologies)

Isolation technologies

Different isolation technologies offer different tradeoffs between security strength, performance, and operational complexity.

In all of these configurations, Claude Code (or your Agent SDK application) runs inside the isolation boundary (the sandbox, container, or VM). The security controls described below restrict what the agent can access from within that boundary.

| Technology              | Isolation strength             | Performance overhead | Complexity  |
|-------------------------|--------------------------------|----------------------|-------------|
| Sandbox runtime         | Good (secure defaults)         | Very low             | Low         |
| Containers (Docker)     | Setup dependent                | Low                  | Medium      |
| gVisor                  | Excellent (with correct setup) | Medium/High          | Medium      |
| VMs (Firecracker, QEMU) | Excellent (with correct setup) | High                 | Medium/High |


[​](#sandbox-runtime)

Sandbox runtime

For lightweight isolation without containers, [sandbox-runtime](https://github.com/anthropics/sandbox-runtime) enforces filesystem and network restrictions at the OS level. The main advantage is simplicity: no Docker configuration, container images, or networking setup required. The proxy and filesystem restrictions are built in. **How it works:**

- **Filesystem**: Uses OS primitives (`bubblewrap` on Linux, `sandbox-exec` on macOS) to restrict read/write access to configured paths
- **Network**: Removes network namespace (Linux) or uses Seatbelt profiles (macOS) to route network traffic through a built-in proxy
- **Configuration**: JSON-based allowlists for domains and filesystem paths

**Setup:**

```python
npm install @anthropic-ai/sandbox-runtime
```

Then create a configuration file specifying allowed paths and domains. **Security considerations:**

1.  **Same-host kernel**: Unlike VMs, sandboxed processes share the host kernel. A kernel vulnerability could theoretically enable escape. For some threat models this is acceptable, but if you need kernel-level isolation, use gVisor or a separate VM.
2.  **No TLS inspection**: The proxy allowlists domains based on the client-supplied hostname and does not terminate or inspect encrypted traffic. Code running inside the sandbox can potentially reach hosts outside the allowlist through traffic-shaping techniques. If your threat model requires stronger guarantees, configure a [TLS-terminating proxy](#traffic-forwarding). See the [sandboxing security limitations](../02-Claude-Code-CLI/sandboxing.md#security-limitations) for more detail. Separately, if the agent has permissive credentials for an allowed domain, ensure it cannot use that domain to trigger other network requests or to exfiltrate data.

For many single-developer and CI/CD use cases, sandbox-runtime raises the bar significantly with minimal setup. The sections below cover containers and VMs for deployments requiring stronger isolation.


[​](#containers)

Containers

Containers provide isolation through Linux namespaces. Each container has its own view of the filesystem, process tree, and network stack, while sharing the host kernel. A security-hardened container configuration might look like this:

```python
docker run \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --security-opt seccomp=/path/to/seccomp-profile.json \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /home/agent:rw,noexec,nosuid,size=500m \
  --network none \
  --memory 2g \
  --cpus 2 \
  --pids-limit 100 \
  --user 1000:1000 \
  -v /path/to/code:/workspace:ro \
  -v /var/run/proxy.sock:/var/run/proxy.sock:ro \
  agent-image
```

Here’s what each option does:

| Option                             | Purpose                                                                                                                                                 |
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--cap-drop ALL`                   | Removes Linux capabilities like `NET_ADMIN` and `SYS_ADMIN` that could enable privilege escalation                                                      |
| `--security-opt no-new-privileges` | Prevents processes from gaining privileges through setuid binaries                                                                                      |
| `--security-opt seccomp=...`       | Restricts available syscalls; Docker’s default blocks ~44, custom profiles can block more                                                               |
| `--read-only`                      | Makes the container’s root filesystem immutable, preventing the agent from persisting changes                                                           |
| `--tmpfs /tmp:...`                 | Provides a writable temporary directory that’s cleared when the container stops                                                                         |
| `--network none`                   | Removes all network interfaces; the agent communicates through the mounted Unix socket below                                                            |
| `--memory 2g`                      | Limits memory usage to prevent resource exhaustion                                                                                                      |
| `--pids-limit 100`                 | Limits process count to prevent fork bombs                                                                                                              |
| `--user 1000:1000`                 | Runs as a non-root user                                                                                                                                 |
| `-v ...:/workspace:ro`             | Mounts code read-only so the agent can analyze but not modify it. **Avoid mounting sensitive host directories like `~/.ssh`, `~/.aws`, or `~/.config`** |
| `-v .../proxy.sock:...`            | Mounts a Unix socket connected to a proxy running outside the container (see below)                                                                     |

**Unix socket architecture:** With `--network none`, the container has no network interfaces at all. The only way for the agent to reach the outside world is through the mounted Unix socket, which connects to a proxy running on the host. This proxy can enforce domain allowlists, inject credentials, and log all traffic. This is the same architecture used by [sandbox-runtime](https://github.com/anthropics/sandbox-runtime). Even if the agent is compromised via prompt injection, it cannot exfiltrate data to arbitrary servers. It can only communicate through the proxy, which controls what domains are reachable. For more details, see the [Claude Code sandboxing blog post](https://www.anthropic.com/engineering/claude-code-sandboxing). **Additional hardening options:**

| Option           | Purpose                                                                                                              |
|------------------|----------------------------------------------------------------------------------------------------------------------|
| `--userns-remap` | Maps container root to unprivileged host user; requires daemon configuration but limits damage from container escape |
| `--ipc private`  | Isolates inter-process communication to prevent cross-container attacks                                              |


[​](#gvisor)

gVisor

Standard containers share the host kernel: when code inside a container makes a system call, it goes directly to the same kernel that runs the host. This means a kernel vulnerability could allow container escape. gVisor addresses this by intercepting system calls in userspace before they reach the host kernel, implementing its own compatibility layer that handles most syscalls without involving the real kernel. If an agent runs malicious code (perhaps due to prompt injection), that code runs in the container and could attempt kernel exploits. With gVisor, the attack surface is much smaller: the malicious code would need to exploit gVisor’s userspace implementation first and would have limited access to the real kernel. To use gVisor with Docker, install the `runsc` runtime and configure the daemon:

/etc/docker/daemon.json

```python
{
  "runtimes": {
    "runsc": {
      "path": "/usr/local/bin/runsc"
    }
  }
}
```

Then run containers with:

```python
docker run --runtime=runsc agent-image
```

**Performance considerations:**

| Workload              | Overhead                                           |
|-----------------------|----------------------------------------------------|
| CPU-bound computation | ~0% (no syscall interception)                      |
| Simple syscalls       | ~2× slower                                         |
| File I/O intensive    | Up to 10-200× slower for heavy open/close patterns |

For multi-tenant environments or when processing untrusted content, the additional isolation is often worth the overhead.


[​](#virtual-machines)

Virtual machines

VMs provide hardware-level isolation through CPU virtualization extensions. Each VM runs its own kernel, creating a strong boundary. A vulnerability in the guest kernel doesn’t directly compromise the host. However, VMs aren’t automatically “more secure” than alternatives like gVisor. VM security depends heavily on the hypervisor and device emulation code. Firecracker is designed for lightweight microVM isolation. It can boot VMs in under 125ms with less than 5 MiB memory overhead, stripping away unnecessary device emulation to reduce attack surface. With this approach, the agent VM has no external network interface. Instead, it communicates through `vsock` (virtual sockets). All traffic routes through vsock to a proxy on the host, which enforces allowlists and injects credentials before forwarding requests.


[​](#cloud-deployments)

Cloud deployments

For cloud deployments, you can combine any of the above isolation technologies with cloud-native network controls:

1.  Run agent containers in a private subnet with no internet gateway
2.  Configure cloud firewall rules (AWS Security Groups, GCP VPC firewall) to block all egress except to your proxy
3.  Run a proxy (such as [Envoy](https://www.envoyproxy.io/) with its `credential_injector` filter) that validates requests, enforces domain allowlists, injects credentials, and forwards to external APIs
4.  Assign minimal IAM permissions to the agent’s service account, routing sensitive access through the proxy where possible
5.  Log all traffic at the proxy for audit purposes


[​](#credential-management)

Credential management

Agents often need credentials to call APIs, access repositories, or interact with cloud services. The challenge is providing this access without exposing the credentials themselves.


[​](#the-proxy-pattern)

The proxy pattern

The recommended approach is to run a proxy outside the agent’s security boundary that injects credentials into outgoing requests. The agent sends requests without credentials, the proxy adds them, and forwards the request to its destination. This pattern has several benefits:

1.  The agent never sees the actual credentials
2.  The proxy can enforce an allowlist of permitted endpoints
3.  The proxy can log all requests for auditing
4.  Credentials are stored in one secure location rather than distributed to each agent


[​](#configuring-claude-code-to-use-a-proxy)

Configuring Claude Code to use a proxy

Claude Code supports two methods for routing sampling requests through a proxy: **Option 1: ANTHROPIC_BASE_URL (simple but only for sampling API requests)**

```python
export ANTHROPIC_BASE_URL="http://localhost:8080"
```

This tells Claude Code and the Agent SDK to send sampling requests to your proxy instead of the Claude API directly. Your proxy receives plaintext HTTP requests, can inspect and modify them (including injecting credentials), then forwards to the real API. **Option 2: HTTP_PROXY / HTTPS_PROXY (system-wide)**

```python
export HTTP_PROXY="http://localhost:8080"
export HTTPS_PROXY="http://localhost:8080"
```

Claude Code and the Agent SDK respect these standard environment variables, routing all HTTP traffic through the proxy. For HTTPS, the proxy creates an encrypted CONNECT tunnel: it cannot see or modify request contents without TLS interception.


[​](#implementing-a-proxy)

Implementing a proxy

You can build your own proxy or use an existing one:

- [Envoy Proxy](https://www.envoyproxy.io/): production-grade proxy with `credential_injector` filter for adding auth headers
- [mitmproxy](https://mitmproxy.org/): TLS-terminating proxy for inspecting and modifying HTTPS traffic
- [Squid](http://www.squid-cache.org/): caching proxy with access control lists
- [LiteLLM](https://github.com/BerriAI/litellm): LLM gateway with credential injection and rate limiting


[​](#credentials-for-other-services)

Credentials for other services

Beyond sampling from the Claude API, agents often need authenticated access to other services, such as git repositories, databases, and internal APIs. There are two main approaches:


[​](#custom-tools)

Custom tools

Provide access through an MCP server or custom tool that routes requests to a service running outside the agent’s security boundary. The agent calls the tool, but the actual authenticated request happens outside. The tool calls to a proxy which injects the credentials. For example, a git MCP server could accept commands from the agent but forward them to a git proxy running on the host, which adds authentication before contacting the remote repository. The agent never sees the credentials. Advantages:

- **No TLS interception**: The external service makes authenticated requests directly
- **Credentials stay outside**: The agent only sees the tool interface, not the underlying credentials


[​](#traffic-forwarding)

Traffic forwarding

For Claude API calls, `ANTHROPIC_BASE_URL` lets you route requests to a proxy that can inspect and modify them in plaintext. But for other HTTPS services (GitHub, npm registries, internal APIs), the traffic is often encrypted end-to-end. Even if you route it through a proxy via `HTTP_PROXY`, the proxy only sees an opaque TLS tunnel and can’t inject credentials. To modify HTTPS traffic to arbitrary services, without using a custom tool, you need a TLS-terminating proxy that decrypts traffic, inspects or modifies it, then re-encrypts it before forwarding. This requires:

1.  Running the proxy outside the agent’s container
2.  Installing the proxy’s CA certificate in the agent’s trust store (so the agent trusts the proxy’s certificates)
3.  Configuring `HTTP_PROXY`/`HTTPS_PROXY` to route traffic through the proxy

This approach handles any HTTP-based service without writing custom tools, but adds complexity around certificate management. Note that not all programs respect `HTTP_PROXY`/`HTTPS_PROXY`. Most tools (curl, pip, npm, git) do, but some may bypass these variables and connect directly. For example, Node.js `fetch()` ignores these variables by default; in Node 24+ you can set `NODE_USE_ENV_PROXY=1` to enable support. To cover tools that ignore these variables, you can use [proxychains](https://github.com/haad/proxychains) to intercept network calls, or configure iptables to redirect outbound traffic to a transparent proxy.

A **transparent proxy** intercepts traffic at the network level, so the client doesn’t need to be configured to use it. Regular proxies require clients to explicitly connect and speak HTTP CONNECT or SOCKS. Transparent proxies (like Squid or mitmproxy in transparent mode) can handle raw redirected TCP connections.

Both approaches still require the TLS-terminating proxy and trusted CA certificate. They just ensure traffic actually reaches the proxy.


[​](#filesystem-configuration)

Filesystem configuration

Filesystem controls determine what files the agent can read and write.


[​](#read-only-code-mounting)

Read-only code mounting

When the agent needs to analyze code but not modify it, mount the directory read-only:

```python
docker run -v /path/to/code:/workspace:ro agent-image
```

Even read-only access to a code directory can expose credentials. Common files to exclude or sanitize before mounting:

| File                                                    | Risk                                  |
|---------------------------------------------------------|---------------------------------------|
| `.env`, `.env.local`                                    | API keys, database passwords, secrets |
| `~/.git-credentials`                                    | Git passwords/tokens in plaintext     |
| `~/.aws/credentials`                                    | AWS access keys                       |
| `~/.config/gcloud/application_default_credentials.json` | Google Cloud ADC tokens               |
| `~/.azure/`                                             | Azure CLI credentials                 |
| `~/.docker/config.json`                                 | Docker registry auth tokens           |
| `~/.kube/config`                                        | Kubernetes cluster credentials        |
| `.npmrc`, `.pypirc`                                     | Package registry tokens               |
| `*-service-account.json`                                | GCP service account keys              |
| `*.pem`, `*.key`                                        | Private keys                          |

Consider copying only the source files needed, or using `.dockerignore`-style filtering.


[​](#writable-locations)

Writable locations

If the agent needs to write files, you have a few options depending on whether you want changes to persist: For ephemeral workspaces in containers, use `tmpfs` mounts that exist only in memory and are cleared when the container stops:

```python
docker run \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /workspace:rw,noexec,size=500m \
  agent-image
```

If you want to review changes before persisting them, an overlay filesystem lets the agent write without modifying underlying files. Changes are stored in a separate layer you can inspect, apply, or discard. For fully persistent output, mount a dedicated volume but keep it separate from sensitive directories.


[​](#further-reading)

Further reading

- [Claude Code security documentation](../13-Enterprise-Admin/security.md)
- [Hosting the Agent SDK](agent-sdk-hosting.md)
- [Handling permissions](agent-sdk-permissions.md)
- [Sandbox runtime](https://github.com/anthropics/sandbox-runtime)
- [The Lethal Trifecta for AI Agents](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/)
- [OWASP Top 10 for LLM Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/)
- [Docker Security Best Practices](https://docs.docker.com/engine/security/)
- [gVisor Documentation](https://gvisor.dev/docs/)
- [Firecracker Documentation](https://firecracker-microvm.github.io/)

---

## Persist sessions to external storage - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/session-storage
Path: 05-Agent-SDK/agent-sdk-session-storage.md

## On this page

- [The SessionStore interface](#the-sessionstore-interface)
- [Quick start](#quick-start)
- [Write your own adapter](#write-your-own-adapter)
- [Reference implementations](#reference-implementations)
  - [Validate your adapter](#validate-your-adapter)
- [Behavior notes](#behavior-notes)
  - [Dual-write architecture](#dual-write-architecture)
  - [Resume from the store](#resume-from-the-store)
  - [Mirror writes are best-effort](#mirror-writes-are-best-effort)
  - [getSessionMessages returns the post-compaction chain](#getsessionmessages-returns-the-post-compaction-chain)
  - [forkSession is not a byte copy](#forksession-is-not-a-byte-copy)
  - [Subagent transcripts](#subagent-transcripts)
  - [Retention](#retention)
- [Supported on](#supported-on)
- [Related resources](#related-resources)

Core concepts

# Persist sessions to external storage

Copy pageCopy page

Mirror Agent SDK session transcripts to your own object store, key-value store, or database so other hosts can resume your sessions.

Copy pageCopy page

By default, the SDK writes session transcripts to JSONL files under `~/.claude/projects/` on the local filesystem. A `SessionStore` adapter lets you mirror those transcripts to your own backend, such as an object store, a key-value store, or a database, so a session created on one host can be resumed on another host running from a matching working directory. Common reasons to use a session store:

- **Multi-host deployments.** Serverless functions, autoscaled workers, and CI runners don’t share a filesystem. A shared store lets replicas resume each other’s sessions.
- **Durability.** Local containers are ephemeral. An external store survives restarts and redeploys.
- **Compliance and audit.** Keep transcripts in storage you already govern, with your own retention rules, encryption, and access controls.


[​](#the-sessionstore-interface)

The `SessionStore` interface

A `SessionStore` is an object with two required methods, `append` and `load`, and four optional methods. The SDK calls `append` to write transcript entries during a query and `load` to read them back for resume.

TypeScript

Python

```python
// Exported from @anthropic-ai/claude-agent-sdk as
// SessionStore, SessionKey, SessionStoreEntry, SessionSummaryEntry.

type SessionKey = {
  projectKey: string;
  sessionId: string;
  subpath?: string;
};

type SessionStore = {
  // Required
  append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
  load(key: SessionKey): Promise<SessionStoreEntry[] | null>;

  // Optional
  listSessions?(
    projectKey: string,
  ): Promise<Array<{ sessionId: string; mtime: number }>>;
  listSessionSummaries?(projectKey: string): Promise<SessionSummaryEntry[]>;
  delete?(key: SessionKey): Promise<void>;
  listSubkeys?(key: {
    projectKey: string;
    sessionId: string;
  }): Promise<string[]>;
};

type SessionSummaryEntry = {
  sessionId: string;
  mtime: number;
  data: Record<string, unknown>;
};
```

```python
# Exported from claude_agent_sdk as
# SessionStore, SessionKey, SessionStoreEntry, SessionSummaryEntry.

class SessionKey(TypedDict):
    project_key: str
    session_id: str
    subpath: NotRequired[str]

class SessionStore(Protocol):
    # Required
    async def append(
        self, key: SessionKey, entries: list[SessionStoreEntry]
    ) -> None: ...
    async def load(self, key: SessionKey) -> list[SessionStoreEntry] | None: ...

    # Optional — omit or raise NotImplementedError
    async def list_sessions(
        self, project_key: str
    ) -> list[SessionStoreListEntry]: ...
    async def list_session_summaries(
        self, project_key: str
    ) -> list[SessionSummaryEntry]: ...
    async def delete(self, key: SessionKey) -> None: ...
    async def list_subkeys(self, key: SessionListSubkeysKey) -> list[str]: ...

class SessionSummaryEntry(TypedDict):
    session_id: str
    mtime: int
    data: dict[str, Any]
```

`SessionKey` addresses one transcript. `projectKey` is a stable, filesystem-safe encoding of the working directory, `sessionId` is the session UUID, and `subpath` is set when the entry belongs to a subagent transcript or sidecar file rather than the main conversation. Because `projectKey` encodes the working directory, resume or continue from the store from a working directory matching the original run’s. In TypeScript, if you set [`CLAUDE_CODE_PROJECT_DIR_NAME`](../02-Claude-Code-CLI/sessions.md#name-the-project-directory-yourself) beside `CLAUDE_CONFIG_DIR` in a query’s [`env` option](agent-sdk-typescript.md#options), the SDK keys that query’s entries, and its `resume` and `continue` lookups, by that name instead. Because standalone helpers such as `listSessions` and `deleteSession` take no `env` and read the process environment, set `CLAUDE_CONFIG_DIR` and the same name in the host process environment too. Requires Agent SDK v0.3.234 or later. Treat `subpath` as an opaque key suffix; it follows the on-disk layout, for example `subagents/agent-<id>`. When `subpath` is undefined the key refers to the main transcript.

| Method                 | Required | Called when                                                                                                                                                                                                                                                                                               |
|:-----------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `append`               | Yes      | After each batch of transcript entries is written locally. Entries are JSON-safe objects, one per line in the local JSONL.                                                                                                                                                                                |
| `load`                 | Yes      | Before the subprocess spawns when `resume` is set or `continue: true` resolves the newest store session, and once per session when listing falls back from `listSessionSummaries`. Return `null` if the session is unknown.                                                                               |
| `listSessions`         | No       | By `listSessions({ sessionStore })` and by `query()`/`startup()` with `continue: true`. If undefined, `continue: true` throws, and `listSessions({ sessionStore })` throws unless `listSessionSummaries` is implemented.                                                                                  |
| `listSessionSummaries` | No       | By `listSessions({ sessionStore })` to read metadata for all sessions in one call. Maintain the summaries inside `append`. If undefined, listing falls back to `listSessions` plus a per-session `load`.                                                                                                  |
| `delete`               | No       | By `deleteSession({ sessionStore })`. Deleting the main key (no `subpath`) must cascade to all subkeys for that session and also remove the session’s summary entry, so a deleted session stops appearing in `listSessionSummaries`. If undefined, deletion is a no-op, which suits append-only backends. |
| `listSubkeys`          | No       | During resume, to discover subagent transcripts. If undefined, only the main transcript is restored.                                                                                                                                                                                                      |

In a `SessionSummaryEntry`, `mtime` is the sidecar’s storage write time and must share a clock source with the `mtime` values `listSessions` returns. `data` is opaque SDK-owned state; persist it verbatim without interpreting it. Build the entries by calling the exported `foldSessionSummary` helper, `fold_session_summary` in Python, on each batch inside `append`. Skip batches whose key has a `subpath`; subagent transcripts must not contribute to the main session’s summary. The fold never sets `mtime`: stamp it at persist time, through the `options.mtime` argument in TypeScript or by overwriting the field on the returned entry in Python. Concurrent `append` calls for the same session can race on the sidecar, so serialize the read-fold-write with a transaction, a compare-and-swap, or a per-session lock; the fold itself is pure. For what the SDK does with the transcript `load` returns, see [Resume from the store](#resume-from-the-store).


[​](#quick-start)

Quick start

The SDK ships an `InMemorySessionStore` for development and testing. The example below runs a query with the store attached, captures the session ID from the result message, then resumes from the store in a second `query()` call. The second call passes the same store instance plus `resume`, so the SDK loads the transcript from the store instead of the local filesystem:

TypeScript

Python

```python
import { query, InMemorySessionStore } from "@anthropic-ai/claude-agent-sdk";

const store = new InMemorySessionStore();

let sessionId: string | undefined;
try {
  for await (const message of query({
    prompt: "List the TypeScript files under src/",
    options: { sessionStore: store },
  })) {
    if (message.type === "result") {
      sessionId = message.session_id;
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, sessionId was already captured by the loop
  // above; connection or process failures yield no result message.
  console.error(`Session ended with an error: ${error}`);
}

// Resume from the store. The agent has full context from the first call.
for await (const message of query({
  prompt: "Summarize what those files do",
  options: { sessionStore: store, resume: sessionId },
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
from claude_agent_sdk import (
    ClaudeAgentOptions,
    InMemorySessionStore,
    ResultMessage,
    query,
)

store = InMemorySessionStore()


async def main():
    session_id = None
    try:
        async for message in query(
            prompt="List the Python files under src/",
            options=ClaudeAgentOptions(session_store=store),
        ):
            if isinstance(message, ResultMessage):
                session_id = message.session_id
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, session_id was already captured by the
        # loop above; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")

    # Resume from the store. The agent has full context from the first call.
    async for message in query(
        prompt="Summarize what those files do",
        options=ClaudeAgentOptions(session_store=store, resume=session_id),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

The second query prints a summary of the files from the first query, which shows the agent resumed with full context from the store.


[​](#write-your-own-adapter)

Write your own adapter

Implement `append` and `load` against your backend. Add `listSessions`, `listSessionSummaries`, `delete`, and `listSubkeys` if you want `listSessions()`, one-call metadata reads, `deleteSession()`, and subagent resume to work against the store. Entries passed to `append` are typed as `SessionStoreEntry` (a `{ type: string; ... }` object). Treat them as opaque JSON-safe values: persist them in order and return them from `load` in the same order. `load` must return entries that are deep-equal to what was appended; byte-equal serialization is not required, so a backend that reorders object keys, such as a binary JSON column type, is fine.


[​](#reference-implementations)

Reference implementations

Both SDK repositories include runnable reference adapters under [`examples/session-stores/`](https://github.com/anthropics/claude-agent-sdk-typescript/tree/main/examples/session-stores) in TypeScript and [`examples/session_stores/`](https://github.com/anthropics/claude-agent-sdk-python/tree/main/examples/session_stores) in Python. There is one adapter per storage type, and each shows how `append` and `load` map onto that kind of backend. They are not published as packages; copy the adapter for the type closest to your backend into your project, install your backend’s client, and adapt it.

| Storage type                          | Storage model                                                                                                   | Example adapter                                                                                                                                                                                                                                            |
|:--------------------------------------|:----------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Object store                          | One part file per `append()`; `load()` lists the parts, sorts them, and concatenates.                           | S3 ([TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript/tree/main/examples/session-stores/s3), [Python](https://github.com/anthropics/claude-agent-sdk-python/blob/main/examples/session_stores/s3_session_store.py))                   |
| Key-value store                       | One list per transcript that `append()` pushes to and `load()` reads in range, plus a sorted index of sessions. | Redis ([TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript/tree/main/examples/session-stores/redis), [Python](https://github.com/anthropics/claude-agent-sdk-python/blob/main/examples/session_stores/redis_session_store.py))          |
| Relational database or document store | One row or document per entry, stored as JSON and ordered by a key assigned on insert.                          | Postgres ([TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript/tree/main/examples/session-stores/postgres), [Python](https://github.com/anthropics/claude-agent-sdk-python/blob/main/examples/session_stores/postgres_session_store.py)) |

Each adapter takes a pre-configured client instance, so you control credentials, TLS, region, and pooling. The following example wires the object-store adapter into `query()` and then resumes from it on another host:

TypeScript

```python
import { query } from "@anthropic-ai/claude-agent-sdk";
import { S3Client } from "@aws-sdk/client-s3";
import { S3SessionStore } from "./S3SessionStore"; // copied from examples/session-stores/s3

const store = new S3SessionStore({
  bucket: "my-claude-sessions",
  prefix: "transcripts",
  client: new S3Client({ region: "us-east-1" }),
});

for await (const message of query({
  prompt: "Hello!",
  options: { sessionStore: store },
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

// Later, possibly on a different host:
for await (const message of query({
  prompt: "Continue where we left off",
  options: { sessionStore: store, resume: "previous-session-id" },
})) {
  // ...
}
```


[​](#validate-your-adapter)

Validate your adapter

Both SDKs ship a conformance suite that asserts the behavioral contract `append`, `load`, and the optional methods must satisfy. Tests for optional methods skip automatically when those methods are not implemented. In TypeScript, copy [`shared/conformance.ts`](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/examples/session-stores/shared/conformance.ts) from the example directory into your test suite. In Python, the suite ships in the package. To run it with pytest, which isn’t an SDK dependency, install pytest first:

```python
pip install pytest
```

Then pass your adapter to the suite in a test file as a zero-argument factory, which `run_session_store_conformance` calls once per contract to build a fresh store:

Python

```python
import pytest
from claude_agent_sdk.testing import run_session_store_conformance


@pytest.mark.anyio
async def test_my_store_conformance():
    await run_session_store_conformance(MyRedisStore)
```

Passing the `MyRedisStore` class itself, as this example does, works when the constructor takes no arguments. For an adapter that takes a pre-configured client, pass a lambda that constructs the store instead. Because the contracts reuse the same session keys, each store the factory returns must start with empty storage, so have the lambda provision isolated backing storage per call, such as a fresh in-memory fake, a unique key prefix, or a new test database.


[​](#behavior-notes)

Behavior notes


[​](#dual-write-architecture)

Dual-write architecture

The Claude Code subprocess always writes each batch of transcript entries to local disk first, and the SDK then forwards the same batch to your store’s `append()`, so the store is a mirror of the local transcript rather than a replacement for it. Which copy outlives the run depends on how the run started:

- **Fresh session, or a resume when the store has nothing for the session**: the local transcript under your config directory outlives the run, and the store receives a copy.
- **Run [resumed from the store](#resume-from-the-store)**: the local copy is deleted at run end, so the store holds the only durable copy.

If you don’t want a fresh session to leave a transcript on local disk, set `CLAUDE_CONFIG_DIR` to a temp directory in `options.env`. A run resumed from the store already deletes its local copy, so it needs no such setting. In TypeScript, spread `process.env` into `env` as well, since the [`env` option](agent-sdk-typescript.md#options) replaces the subprocess environment. If your app signs in through files in the config directory, such as OAuth credentials or an `apiKeyHelper` in your user `settings.json`, copy those files into the temp directory first, or set `ANTHROPIC_API_KEY` in `env` instead. Otherwise the run fails with `Not logged in`. Two options conflict with the mirror, and the SDK throws at startup if you combine either with a store:

- **`persistSession: false`** in TypeScript: turns off the local writes the mirror is built from. The Python SDK has no equivalent option.
- **File checkpointing**, `enableFileCheckpointing` in TypeScript or `enable_file_checkpointing` in Python: writes its file backups straight to local disk, and the SDK doesn’t mirror them to the store.


[​](#resume-from-the-store)

Resume from the store

When you pass `resume`, or `continue: true` in TypeScript or `continue_conversation=True` in Python, together with a store, the SDK asks the store for a transcript before it spawns the subprocess:

- **`resume`**: the SDK asks for the session whose ID you passed.
- **`continue: true`** or **`continue_conversation=True`**: the SDK asks for the store’s newest session.

When the store returns the transcript, the SDK writes it into a temporary config directory, runs the subprocess with `CLAUDE_CONFIG_DIR` pointing there, and deletes the directory when the run ends. The local transcript that run writes is deleted with it, which is why the store holds the only durable copy on this path. The SDK also seeds the temporary directory with files from your real config directory. What it copies differs by language:

- **TypeScript**: credentials, `.claude.json`, and your user `settings.json`. From `settings.json` it strips the keys that misbehave under a temporary config directory: `enabledPlugins`, `extraKnownMarketplaces`, its [`additionalMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#extraknownmarketplaces) alias, and any `CLAUDE_CONFIG_DIR` in the file’s `env` block. Before Agent SDK v0.3.232, the SDK didn’t strip the alias. Auth configured in settings, such as [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper), works when you resume from the store. Before Agent SDK v0.3.222, the TypeScript SDK copied only credentials and `.claude.json`.
- **Python**: credentials and `.claude.json` only, so an app that authenticates through `apiKeyHelper` in your user `settings.json` fails with `Not logged in` when resuming from a store. An `apiKeyHelper` in managed or project settings still works, because Claude Code reads those files from locations that `CLAUDE_CONFIG_DIR` doesn’t affect.

When the store has nothing for the session, the SDK runs under your real config directory instead, and the outcome depends on which option you passed:

- **`resume`**: both SDKs pass the ID through to the subprocess, which resumes the local transcript exactly as `resume` does without a store.
- **`continue: true`** in TypeScript: the SDK starts a fresh session.
- **`continue_conversation=True`** in Python: the SDK continues from the newest local session.


[​](#mirror-writes-are-best-effort)

Mirror writes are best-effort

If `append()` rejects, the SDK retries the batch up to two more times with a short backoff, for at most three attempts in total. A call that times out isn’t retried, since the original call may still land. If the batch still fails, the SDK logs the error, emits a `{ type: "system", subtype: "mirror_error" }` message into the iterator, drops the batch, and continues the query. Because a retried batch can re-deliver entries that already landed, deduplicate by `entry.uuid` in your `append()` implementation. A store outage doesn’t interrupt the agent, since the subprocess writes locally first. Monitor for `mirror_error` if you need to detect store data loss. On a run [resumed from the store](#resume-from-the-store), a dropped batch has no surviving copy once the run ends.


[​](#getsessionmessages-returns-the-post-compaction-chain)

`getSessionMessages` returns the post-compaction chain

`getSessionMessages({ sessionStore })` returns the linked message chain the agent would see on resume. After auto-compaction, earlier turns are replaced by a summary, so a session whose store holds 503 raw entries may return 18 messages from `getSessionMessages`. For the full raw history, including pre-compaction turns and metadata entries, call `store.load(key)` directly.


[​](#forksession-is-not-a-byte-copy)

`forkSession` is not a byte copy

`forkSession({ sessionStore })` reads the source entries, rewrites every `sessionId` field and remaps message UUIDs, then appends the transformed entries under a new key. An adapter-level copy or `CopyObject` shortcut would produce a transcript that still references the old session ID, so the SDK does not use one.


[​](#subagent-transcripts)

Subagent transcripts

Subagent transcripts are mirrored under `subpath: "subagents/agent-<id>"`. `listSubagents({ sessionStore })` requires the adapter to implement `listSubkeys`; `getSubagentMessages({ sessionStore })` uses it when available but falls back to the direct subpath when it is undefined. Resume also calls `listSubkeys` to restore subagent files; without it, only the main transcript is materialized.


[​](#retention)

Retention

The SDK never deletes from your store on its own. Retention is the adapter’s responsibility: use your backend’s expiry or lifecycle mechanism, or run scheduled cleanup, according to your compliance requirements. Local transcripts under `CLAUDE_CONFIG_DIR` are swept independently by the `cleanupPeriodDays` setting, following the [retention sweep rules](../02-Claude-Code-CLI/claude-directory.md#cleaned-up-automatically). A run [resumed from the store](#resume-from-the-store) leaves no local transcript, so for those runs your store’s retention is the only retention there is.


[​](#supported-on)

Supported on

The following TypeScript SDK functions accept a `sessionStore` option and operate against the store instead of the local filesystem when it is provided:

- [`query()`](agent-sdk-typescript.md#query)
- [`startup()`](agent-sdk-typescript.md#startup)
- [`listSessions()`](agent-sdk-typescript.md#listsessions)
- [`getSessionInfo()`](agent-sdk-typescript.md#getsessioninfo)
- [`getSessionMessages()`](agent-sdk-typescript.md#getsessionmessages)
- [`renameSession()`](agent-sdk-typescript.md#renamesession)
- [`tagSession()`](agent-sdk-typescript.md#tagsession)
- [`deleteSession()`](agent-sdk-typescript.md)
- [`forkSession()`](agent-sdk-typescript.md)
- [`listSubagents()`](agent-sdk-typescript.md)
- [`getSubagentMessages()`](agent-sdk-typescript.md)

In the Python SDK, set `session_store` in [`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) to run `query()` against a store. The remaining operations each have a store-backed Python function that takes the store as an argument: `list_sessions_from_store()`, `get_session_info_from_store()`, `get_session_messages_from_store()`, `list_subagents_from_store()`, `get_subagent_messages_from_store()`, `rename_session_via_store()`, `tag_session_via_store()`, `delete_session_via_store()`, and `fork_session_via_store()`. `startup()` has no Python equivalent. The standalone functions documented in the [Python SDK reference](agent-sdk-python.md#functions), such as `list_sessions()`, read local session files.


[​](#related-resources)

Related resources

- [Work with sessions](agent-sdk-sessions.md): Continue, resume, and fork without a custom store
- [Host the SDK](agent-sdk-hosting.md): Deployment patterns for multi-host environments
- [TypeScript `Options`](agent-sdk-typescript.md#options): Full option reference
- [Reference implementations](#reference-implementations): Runnable example adapters for an object store, a key-value store, and a database, in both SDK repositories

---

## Work with sessions - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/sessions
Path: 05-Agent-SDK/agent-sdk-sessions.md

## On this page

- [Choose an approach](#choose-an-approach)
  - [Continue, resume, and fork](#continue-resume-and-fork)
- [Automatic session management](#automatic-session-management)
  - [Python: ClaudeSDKClient](#python-claudesdkclient)
  - [TypeScript: continue: true](#typescript-continue-true)
- [Use session options with query()](#use-session-options-with-query)
  - [Capture the session ID](#capture-the-session-id)
  - [Resume by ID](#resume-by-id)
  - [Fork to explore alternatives](#fork-to-explore-alternatives)
- [Resume across hosts](#resume-across-hosts)
- [Related resources](#related-resources)

Core concepts

# Work with sessions

Copy pageCopy page

How sessions persist agent conversation history, and when to use continue, resume, and fork to return to a prior run.

Copy pageCopy page

A session is the conversation history the SDK accumulates while your agent works. It contains your prompt, every tool call the agent made, every tool result, and every response. The SDK writes it to disk automatically so you can return to it later. Returning to a session means the agent has full context from before: files it already read, analysis it already performed, decisions it already made. You can ask a follow-up question, recover from an interruption, or branch off to try a different approach.

Sessions persist the **conversation**, not the filesystem. To snapshot and revert file changes the agent made, use [file checkpointing](agent-sdk-file-checkpointing.md).

This guide covers how to pick the right approach for your app, the SDK interfaces that track sessions automatically, how to capture session IDs and use `resume` and `fork` manually, and what to know about resuming sessions across hosts.


[​](#choose-an-approach)

Choose an approach

How much session handling you need depends on your application’s shape. Session management comes into play when you send multiple prompts that should share context. Within a single `query()` call, the agent already takes as many turns as it needs, and permission prompts and `AskUserQuestion` are [handled in-loop](agent-sdk-user-input.md) (they don’t end the call).

| What you’re building                                    | What to use                                                                                                                                                                                                                                                                              |
|:--------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| One-shot task: single prompt, no follow-up              | Nothing extra. One `query()` call handles it.                                                                                                                                                                                                                                            |
| Multi-turn chat in one process                          | [`ClaudeSDKClient` (Python) or `continue: true` (TypeScript)](#automatic-session-management). The SDK tracks the session for you with no ID handling.                                                                                                                                    |
| Pick up where you left off after a process restart      | `continue_conversation=True` (Python) / `continue: true` (TypeScript). Resumes the most recent session in the directory, no ID needed.                                                                                                                                                   |
| Resume a specific past session (not the most recent)    | Capture the session ID and pass it to `resume`.                                                                                                                                                                                                                                          |
| Try an alternative approach without losing the original | Fork the session.                                                                                                                                                                                                                                                                        |
| Stateless task, don’t want anything written to disk     | Set [`persistSession: false`](agent-sdk-typescript.md#options) (TypeScript only). The session exists only in memory for the duration of the call. In Python, set [`CLAUDE_CODE_SKIP_PROMPT_HISTORY`](../02-Claude-Code-CLI/env-vars.md) in the `env` option to suppress transcript writes instead. |


[​](#continue-resume-and-fork)

Continue, resume, and fork

Continue, resume, and fork are option fields you set on `query()` ([`ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions) in Python, [`Options`](agent-sdk-typescript.md#options) in TypeScript). **Continue** and **resume** both pick up an existing session and add to it. The difference is how they find that session:

- **Continue** finds the most recent session in the current directory. You don’t track anything. Works well when your app runs one conversation at a time.
- **Resume** takes a specific session ID. You track the ID. Required when you have multiple sessions (for example, one per user in a multi-user app) or want to return to one that isn’t the most recent.

**Fork** is different: it creates a new session that starts with a copy of the original’s history. The original stays unchanged. Use fork to try a different direction while keeping the option to go back.


[​](#automatic-session-management)

Automatic session management

Both SDKs offer an interface that tracks session state for you across calls, so you don’t pass IDs around manually. Use these for multi-turn conversations within a single process.


[​](#python-claudesdkclient)

Python: `ClaudeSDKClient`

[`ClaudeSDKClient`](agent-sdk-python.md#claudesdkclient) handles session IDs internally. Each call to `client.query()` automatically continues the same session. Call [`client.receive_response()`](agent-sdk-python.md#claudesdkclient) to iterate over the messages for the current query. Use the client as an async context manager so connection setup and teardown are handled for you, or call `connect()` and `disconnect()` manually. This example runs two queries against the same `client`. The first asks the agent to analyze a module; the second asks it to refactor that module. Because both calls go through the same client instance, the second query has full context from the first without any explicit `resume` or session ID:

Python

```python
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    ResultMessage,
    TextBlock,
)


def print_response(message):
    """Print only the human-readable parts of a message."""
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, TextBlock):
                print(block.text)
    elif isinstance(message, ResultMessage):
        cost = (
            f"${message.total_cost_usd:.4f}"
            if message.total_cost_usd is not None
            else "N/A"
        )
        print(f"[done: {message.subtype}, cost: {cost}]")


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Edit", "Glob", "Grep"],
    )

    async with ClaudeSDKClient(options=options) as client:
        # First query: client captures the session ID internally
        await client.query("Analyze the auth module")
        async for message in client.receive_response():
            print_response(message)

        # Second query: automatically continues the same session
        await client.query("Now refactor it to use JWT")
        async for message in client.receive_response():
            print_response(message)


asyncio.run(main())
```

Each query prints the agent’s text response followed by a status line from the result message, such as `[done: success, cost: $0.0042]`. See the [Python SDK reference](agent-sdk-python.md#choosing-between-query-and-claudesdkclient) for details on when to use `ClaudeSDKClient` vs the standalone `query()` function.


[​](#typescript-continue-true)

TypeScript: `continue: true`

The TypeScript SDK doesn’t have a session-holding client object like Python’s `ClaudeSDKClient`. Instead, pass `continue: true` on each subsequent `query()` call and the SDK picks up the most recent session in the current directory. No ID tracking required. This example makes two separate `query()` calls. The first creates a fresh session; the second sets `continue: true`, which tells the SDK to find and resume the most recent session on disk. The agent has full context from the first call:

TypeScript

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// First query: creates a new session
try {
  for await (const message of query({
    prompt: "Analyze the auth module",
    options: { allowedTools: ["Read", "Glob", "Grep"] }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the follow-up query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Second query: continue: true resumes the most recent session
for await (const message of query({
  prompt: "Now refactor it to use JWT",
  options: {
    continue: true,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

The experimental [V2 session API](agent-sdk-typescript-v2-preview.md), which provided `createSession()` with a `send` / `stream` pattern, was removed in TypeScript Agent SDK 0.3.142. Use the `query()` function and the session options described on this page instead.


[​](#use-session-options-with-query)

Use session options with `query()`


[​](#capture-the-session-id)

Capture the session ID

Resume and fork require a session ID. Read it from the `session_id` field on the result message ([`ResultMessage`](agent-sdk-python.md#resultmessage) in Python, [`SDKResultMessage`](agent-sdk-typescript.md#sdkresultmessage) in TypeScript), which is present on every result regardless of success or error. In TypeScript the ID is also available earlier as a direct field on the init `SystemMessage`; in Python it’s nested inside `SystemMessage.data`.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    session_id = None

    try:
        async for message in query(
            prompt="Analyze the auth module and suggest improvements",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Glob", "Grep"],
            ),
        ):
            if isinstance(message, ResultMessage):
                session_id = message.session_id
                if message.subtype == "success":
                    print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the loop above already captured session_id;
        # connection or process failures yield no result message, so session_id stays None.
        print(f"Session ended with an error: {error}")

    print(f"Session ID: {session_id}")
    return session_id


session_id = asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

let sessionId: string | undefined;

try {
  for await (const message of query({
    prompt: "Analyze the auth module and suggest improvements",
    options: { allowedTools: ["Read", "Glob", "Grep"] }
  })) {
    if (message.type === "result") {
      sessionId = message.session_id;
      if (message.subtype === "success") {
        console.log(message.result);
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the loop above already captured sessionId;
  // connection or process failures yield no result message, so sessionId stays undefined.
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Session ID: ${sessionId}`);
```

When the query completes, the script prints the agent’s response followed by a line such as `Session ID: 5b3f2c1a-8d4e-4f6b-9a7c-2e1d0f9b8a6c`. In the next sections, you pass this ID to `resume`.


[​](#resume-by-id)

Resume by ID

Pass a session ID to `resume` to return to that specific session. The agent picks up with full context from wherever the session left off. Common reasons to resume:

- **Follow up on a completed task.** The agent already analyzed something; now you want it to act on that analysis without re-reading files.
- **Recover from a limit.** The first run ended with `error_max_turns` or `error_max_budget_usd` (see [Handle the result](agent-sdk-agent-loop.md#handle-the-result)); resume with a higher limit. In a single-shot `query()` call the SDK raises after yielding that error result, so catch the error before resuming.
- **Restart your process.** You captured the ID before shutdown and want to restore the conversation.

This example resumes the session from [Capture the session ID](#capture-the-session-id) with a follow-up prompt. Because you’re resuming, the agent already has the prior analysis in context:

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

session_id = "..."  # The ID you captured in the previous example


async def main():
    # Earlier session analyzed the code; now build on that analysis
    async for message in query(
        prompt="Now implement the refactoring you suggested",
        options=ClaudeAgentOptions(
            resume=session_id,
            allowed_tools=["Read", "Edit", "Write", "Glob", "Grep"],
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const sessionId = "..."; // The ID you captured in the previous example

// Earlier session analyzed the code; now build on that analysis
for await (const message of query({
  prompt: "Now implement the refactoring you suggested",
  options: {
    resume: sessionId,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

You should see a response that builds on the earlier analysis instead of starting fresh. That confirms the agent resumed the session with its prior context intact.

Claude Code stores sessions under `~/.claude/projects/<encoded-cwd>/*.jsonl`. If you set the `CLAUDE_CONFIG_DIR` environment variable, look under `$CLAUDE_CONFIG_DIR/projects/` instead.To find your session’s directory, replace every non-alphanumeric character in the absolute working directory with `-`: `/Users/me/proj` becomes `-Users-me-proj`. For a working directory whose converted name exceeds 200 characters, Claude Code [truncates the name and appends a hash](../02-Claude-Code-CLI/sessions.md#where-transcripts-are-stored), so match the first 200 characters of the converted name when you list `projects/`.If you set [`CLAUDE_CODE_PROJECT_DIR_NAME`](../02-Claude-Code-CLI/sessions.md#name-the-project-directory-yourself) beside `CLAUDE_CONFIG_DIR`, look under that name in `projects/` instead. Requires TypeScript Agent SDK v0.3.234 or later, or Python Agent SDK v0.2.140 or later.You can resume from any working directory:

- **Cross-directory lookup**: Claude Code searches beyond the current project directory to find the ID; see [Resume a session](../02-Claude-Code-CLI/sessions.md#resume-a-session) for the exact lookup order and how duplicate copies are handled.
- **Same machine only**: the session file still needs to exist on the current machine.

Before v2.1.223, the lookup was scoped to the current project directory and its git worktrees; SDK versions that bundle an older CLI still behave this way.

To resume sessions across machines or in serverless environments, mirror transcripts to shared storage with a [`SessionStore` adapter](agent-sdk-session-storage.md).


[​](#fork-to-explore-alternatives)

Fork to explore alternatives

Forking creates a new session that starts with a copy of the original’s history but diverges from that point. The fork gets its own session ID; the original’s ID and history stay unchanged. You end up with two independent sessions you can resume separately.

Forking branches the conversation history, not the filesystem. If a forked agent edits files, those changes are real and visible to any session working in the same directory. To branch and revert file changes, use [file checkpointing](agent-sdk-file-checkpointing.md).

This example builds on [Capture the session ID](#capture-the-session-id): you’ve already analyzed an auth module in `session_id` and want to explore OAuth2 without losing the JWT-focused thread. The first block forks the session and captures the fork’s ID (`forked_id`); the second block resumes the original `session_id` to continue down the JWT path. You now have two session IDs pointing at two separate histories:

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

session_id = "..."  # The ID you captured in the previous example


async def main():
    # Fork: branch from session_id into a new session
    forked_id = None
    try:
        async for message in query(
            prompt="Instead of JWT, outline how OAuth2 would work for the auth module",
            options=ClaudeAgentOptions(
                resume=session_id,
                fork_session=True,
                max_turns=5,
            ),
        ):
            if isinstance(message, ResultMessage):
                forked_id = message.session_id  # The fork's ID, distinct from session_id
                if message.subtype == "success":
                    print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, forked_id was already captured by the
        # loop above; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")

    print(f"Forked session: {forked_id}")

    # Original session is untouched; resuming it continues the JWT thread
    try:
        async for message in query(
            prompt="Continue with the JWT approach",
            options=ClaudeAgentOptions(resume=session_id),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const sessionId = "..."; // The ID you captured in the previous example

// Fork: branch from sessionId into a new session
let forkedId: string | undefined;

try {
  for await (const message of query({
    prompt: "Instead of JWT, outline how OAuth2 would work for the auth module",
    options: {
      resume: sessionId,
      forkSession: true,
      maxTurns: 5
    }
  })) {
    if (message.type === "system" && message.subtype === "init") {
      forkedId = message.session_id; // The fork's ID, distinct from sessionId
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, forkedId was already captured by the loop
  // above; connection or process failures yield no result message.
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Forked session: ${forkedId}`);

// Original session is untouched; resuming it continues the JWT thread
try {
  for await (const message of query({
    prompt: "Continue with the JWT approach",
    options: { resume: sessionId }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result.
  console.error(`Session ended with an error: ${error}`);
}
```

You should see that `forkedId` differs from the original session ID. Resuming the original session still continues the JWT thread, which confirms the fork did not modify the original history.


[​](#resume-across-hosts)

Resume across hosts

Session files are local to the machine that created them. To resume a session on a different host (CI workers, ephemeral containers, serverless), pick the approach that fits:

- **Pass a session store.** Attach a [`sessionStore` / `session_store` adapter](agent-sdk-session-storage.md) so the SDK mirrors transcripts to your own backend and another host can resume them. The store lookup key derives from the working directory, so resume from a `cwd` matching the original run’s.
- **Move the session file.** Persist `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl` from the first run and restore it inside any directory under `~/.claude/projects/` on the new host before calling `resume`. Claude Code searches beyond the current project directory to find the ID; see [Resume a session](../02-Claude-Code-CLI/sessions.md#resume-a-session) for the exact lookup order and how duplicate copies are handled. Before v2.1.223, the lookup was scoped to the current project directory and its git worktrees; SDK versions that bundle an older CLI still behave this way.
- **Don’t rely on session resume.** Capture the results you need (analysis output, decisions, file diffs) as application state and pass them into a fresh session’s prompt. This is often more robust than shipping transcript files around.

Both SDKs expose functions for enumerating sessions on disk and reading their messages: [`listSessions()`](agent-sdk-typescript.md#listsessions) and [`getSessionMessages()`](agent-sdk-typescript.md#getsessionmessages) in TypeScript, [`list_sessions()`](agent-sdk-python.md#list_sessions) and [`get_session_messages()`](agent-sdk-python.md#get_session_messages) in Python. Use them to build custom session pickers, cleanup logic, or transcript viewers. Both SDKs also expose functions for looking up and mutating individual sessions: [`get_session_info()`](agent-sdk-python.md#get_session_info), [`rename_session()`](agent-sdk-python.md#rename_session), and [`tag_session()`](agent-sdk-python.md#tag_session) in Python, and [`getSessionInfo()`](agent-sdk-typescript.md#getsessioninfo), [`renameSession()`](agent-sdk-typescript.md#renamesession), and [`tagSession()`](agent-sdk-typescript.md#tagsession) in TypeScript. Use them to organize sessions by tag or give them human-readable titles.


[​](#related-resources)

Related resources

- [How the agent loop works](agent-sdk-agent-loop.md): Understand turns, messages, and context accumulation within a session
- [File checkpointing](agent-sdk-file-checkpointing.md): Snapshot and revert file changes the agent made within a session
- [Python `ClaudeAgentOptions`](agent-sdk-python.md#claudeagentoptions): Full session option reference for Python
- [TypeScript `Options`](agent-sdk-typescript.md#options): Full session option reference for TypeScript

---

## Extend agents with skills - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/skills
Path: 05-Agent-SDK/agent-sdk-skills.md

## On this page

- [How skills work with the Agent SDK](#how-skills-work-with-the-agent-sdk)
- [Use skills with the Agent SDK](#use-skills-with-the-agent-sdk)
  - [Set up skills in a session](#set-up-skills-in-a-session)
  - [Confirm skills loaded](#confirm-skills-loaded)
  - [Allow only specific skills](#allow-only-specific-skills)
- [Commands in Agent SDK sessions](#commands-in-agent-sdk-sessions)
  - [Discover available commands](#discover-available-commands)
  - [Dispatch commands by name](#dispatch-commands-by-name)
  - [Compact history with /compact](#compact-history-with-%2Fcompact)
  - [Reset context with /clear](#reset-context-with-%2Fclear)
- [Create skills](#create-skills)
  - [Choose a discovery level](#choose-a-discovery-level)
  - [Create and dispatch your first skill](#create-and-dispatch-your-first-skill)
- [Pre-approve tools for skills](#pre-approve-tools-for-skills)
- [Troubleshooting](#troubleshooting)
  - [Skills not found](#skills-not-found)
  - [Skill not being used](#skill-not-being-used)
  - [Invalid skill name error](#invalid-skill-name-error)
  - [Additional troubleshooting](#additional-troubleshooting)
- [Next steps](#next-steps)
- [Related resources](#related-resources)

Customize behavior

# Extend agents with skills

Copy pageCopy page

Control which skills Claude can invoke in Claude Agent SDK sessions, dispatch commands by name, and author skills your sessions discover

Copy pageCopy page

Agent Skills extend Claude with specialized capabilities that Claude invokes when relevant. Skills are packaged as `SKILL.md` files containing instructions, descriptions, and optional supporting resources. This page also covers [commands in Agent SDK sessions](#commands-in-agent-sdk-sessions). For more information about skills, including benefits, architecture, and authoring guidelines, see the [Agent Skills overview](../04-API-Reference/Agents-Tools/agents-and-tools-agent-skills-overview.md).


[​](#how-skills-work-with-the-agent-sdk)

How skills work with the Agent SDK

When using the Claude Agent SDK, skills are:

- **Defined as filesystem artifacts**: you create each skill as a `SKILL.md` file in its own directory, such as `.claude/skills/<name>/SKILL.md`
- **Loaded from filesystem**: the SDK loads skills from the filesystem locations governed by `settingSources` (TypeScript) or `setting_sources` (Python)
- **Automatically discovered**: once filesystem settings load, the SDK discovers skill metadata at startup from user and project directories, and loads the full content when Claude invokes the skill
- **Model-invoked**: Claude autonomously chooses when to use them based on context
- **User-invoked**: you dispatch a skill directly by sending `/<name>` in a prompt. See [Commands in Agent SDK sessions](#commands-in-agent-sdk-sessions)
- **Scoped via the `skills` option**: discovered skills are enabled by default. Pass a list of skill names, `"all"`, or `[]` to control which skills Claude can invoke

Unlike subagents, which you can define in the [`agents` option](agent-sdk-subagents.md#programmatic-definition-recommended), you create skills as files on disk. The SDK doesn’t provide a programmatic API for registering them.

Skills are discovered through the filesystem setting sources. With default `query()` options, the SDK loads user and project sources, so skills in `~/.claude/skills/`, `<cwd>/.claude/skills/`, and `.claude/skills/` in any parent directory of `<cwd>` up to the repository root are available. The project source also covers `<dir>/.claude/skills/` in each directory you pass through `additionalDirectories` (TypeScript) or `add_dirs` (Python), because the SDK passes those directories to Claude Code as [`--add-dir`](../08-Plugins-Skills/skills.md#skills-from-additional-directories). If you set `settingSources` explicitly, include `'project'` to keep project and added-directory skills and `'user'` to keep your personal skills, or use the [`plugins` option](agent-sdk-plugins.md) to load skills from a specific path.


[​](#use-skills-with-the-agent-sdk)

Use skills with the Agent SDK

Set the `skills` option on `query()` to control which skills Claude can invoke in the session. When omitted, discovered skills are enabled and the Skill tool is available, matching CLI behavior. Pass `"all"` to let Claude invoke every discovered skill, a list of skill names to allow only those, or `[]` to let Claude invoke none. For example, to let Claude invoke only two named skills:

Python

TypeScript

```python
options = ClaudeAgentOptions(skills=["pdf", "docx"])
```

```python
const options = { skills: ["pdf", "docx"] };
```


[​](#set-up-skills-in-a-session)

Set up skills in a session

When you set `skills`, the SDK adds the Skill tool to `allowedTools` automatically. If you also pass an explicit `tools` list, include `"Skill"` in that list so Claude can invoke skills. Once configured, Claude automatically discovers skills from the filesystem and invokes them when relevant to the user’s request. The following example enables every discovered skill in a session and pre-approves the tools that skills commonly need. The example sets `cwd` to the process’s current working directory, so run it from inside a project that has a `.claude/skills/` directory in the current directory or any parent up to the repository root:

Python

TypeScript

```python
import asyncio
import os

from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    options = ClaudeAgentOptions(
        cwd=os.getcwd(),  # .claude/skills/ here or in a parent directory
        setting_sources=["user", "project"],  # Load skills from filesystem
        skills="all",  # Let Claude invoke every discovered skill
        allowed_tools=["Read", "Write", "Bash"],
    )

    async for message in query(
        prompt="Help me process this PDF document", options=options
    ):
        print(message)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Help me process this PDF document",
  options: {
    cwd: process.cwd(), // .claude/skills/ here or in a parent directory
    settingSources: ["user", "project"], // Load skills from filesystem
    skills: "all", // Let Claude invoke every discovered skill
    allowedTools: ["Read", "Write", "Bash"]
  }
})) {
  console.log(message);
}
```


[​](#confirm-skills-loaded)

Confirm skills loaded

Near the start of the stream, the SDK yields a system message with subtype `init`. Check its `skills` array to confirm your skills loaded before Claude starts working. The array includes the user-invocable skills that you have defined with a `description` or `when_to_use` frontmatter field, along with [bundled skills included with Claude Code](../08-Plugins-Skills/skills.md#bundled-skills). The array lists user-invocable skills only. A skill with [`user-invocable: false`](../08-Plugins-Skills/skills.md#control-who-invokes-a-skill) in its frontmatter loads and remains available to Claude, but doesn’t appear in the array. The array lists the same skills whether or not they’re in your `skills` list.


[​](#allow-only-specific-skills)

Allow only specific skills

To let Claude invoke only specific skills, pass their names in the `skills` list. Names match the `name` field in `SKILL.md` or the skill’s directory name. Use `plugin:skill` for plugin-provided skills. The list takes exact skill names only. If an entry can’t work as an exact name, `query()` rejects the list before the session starts. See [Invalid skill name error](#invalid-skill-name-error) for the name rules and the error each SDK raises. The model doesn’t see unlisted skills and the Skill tool rejects them, while their files remain on disk and stay reachable through Read and Bash. Restricting the list doesn’t restrict [dispatch by name](#dispatch-commands-by-name). To let Claude invoke every discovered skill, pass `skills: "all"` rather than a wildcard.


[​](#commands-in-agent-sdk-sessions)

Commands in Agent SDK sessions

This section is the SDK’s command documentation. A command is anything you run by sending `/<name>` in a prompt. Entries on the command surface differ in what backs them:

- **Built-in commands**: execute logic coded into the Claude Code process the SDK runs, for example `/compact`
- **Bundled skills**: prompt artifacts included with Claude Code, for example `/code-review`
- **Your skills**: prompt artifacts that you author, each a directory holding a `SKILL.md` file. A user-invocable skill’s name joins the surface automatically, so dispatching your own `/security-check` and running a built-in work the same way
- **Custom command files**: an older artifact form with the same behavior, flat Markdown files in `.claude/commands/` whose filenames become command names. Skills are their recommended successor

By default, both you and Claude can invoke any skill. You can restrict either path through the skill’s [frontmatter](../08-Plugins-Skills/skills.md#control-who-invokes-a-skill). For definitions of command and skill, see the glossary’s [Command](../02-Claude-Code-CLI/glossary.md#command) and [Skill](../02-Claude-Code-CLI/glossary.md#skill) entries. See [Commands in Claude Code](../02-Claude-Code-CLI/commands.md) for every built-in and [Extend Claude with skills](../08-Plugins-Skills/skills.md) for the complete guide to both artifact forms.


[​](#discover-available-commands)

Discover available commands

You can dispatch commands that work without an interactive terminal through the SDK. The `system/init` message lists the ones available in your session in its `slash_commands` field. Commands that need an interactive terminal, such as `/theme` and `/terminal-setup`, don’t appear in the list. Access the field when your session starts:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Hello Claude",
  options: { maxTurns: 1 }
})) {
  if (message.type === "system" && message.subtype === "init") {
    console.log("Available commands:", message.slash_commands);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage


async def main():
    async for message in query(prompt="Hello Claude", options=ClaudeAgentOptions(max_turns=1)):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            print("Available commands:", message.data["slash_commands"])


asyncio.run(main())
```

The printed list mixes built-in commands, bundled skills, your user-invocable skills, and `.claude/commands/` files:

```python
Available commands: ["clear", "compact", "context", "usage", "code-review", "verify", "security-check", ...]
```

A skill with [`user-invocable: false`](../08-Plugins-Skills/skills.md#control-who-invokes-a-skill) in its frontmatter doesn’t appear in this list or in the `skills` array from [Confirm skills loaded](#confirm-skills-loaded). Sessions that configure [MCP servers](agent-sdk-mcp.md) can also expose [MCP prompts as commands](../06-MCP-Tools/General/mcp.md#use-mcp-prompts-as-commands).


[​](#dispatch-commands-by-name)

Dispatch commands by name

Send a command by including it in your prompt string, the same way you send regular text. Dispatch doesn’t depend on the `skills` option. Sending `/<name>` runs a user-invocable skill even when your `skills` list omits it. Commands that act on conversation history, such as `/compact`, need prior messages to work with. A `/<name>` that matches neither a command in the session nor a built-in Claude Code command doesn’t fail the query. Claude Code sends the prompt to Claude as an ordinary message, with a note that the command didn’t run, so the query spends a model turn and returns Claude’s reply. Before v2.1.274, a `/<name>` that matched nothing returned `Unknown command: /<name>` as the result without a model turn. A `/<name>` that matches a built-in Claude Code command that isn’t available in the session, such as `/theme`, returns `/theme isn't available in this environment.` as the result without a model turn.

A command can hit the `maxTurns` / `max_turns` limit like any other prompt, ending the query with an error result instead of `success`. For the error-result contract, see [Handle the result](agent-sdk-agent-loop.md#handle-the-result). If your command might hit the limit, wrap the loop in a `try`/`catch` in TypeScript or `try`/`except` in Python, as shown in [Single Message Input](agent-sdk-streaming-vs-single-mode.md#single-message-input), or set `maxTurns` high enough for the work to complete.


[​](#compact-history-with-/compact)

Compact history with `/compact`

The `/compact` command reduces the size of your conversation history by summarizing older messages while preserving important context. Compaction needs an existing conversation with enough prior messages to summarize. This example has a conversation first, then compacts it and reads the `compact_boundary` system message that reports the result:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Compaction needs existing history, so have a conversation first
try {
  for await (const message of query({
    prompt: "Explain what this project does",
    options: { maxTurns: 2 }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the follow-up query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Compact the same conversation
for await (const message of query({
  prompt: "/compact",
  options: { continue: true, maxTurns: 1 }
})) {
  if (message.type === "system" && message.subtype === "compact_boundary") {
    console.log("Compaction completed");
    console.log("Pre-compaction tokens:", message.compact_metadata.pre_tokens);
    console.log("Trigger:", message.compact_metadata.trigger);
    // Example output:
    // Compaction completed
    // Pre-compaction tokens: 1842
    // Trigger: manual
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage


async def main():
    # Compaction needs existing history, so have a conversation first
    try:
        async for message in query(
            prompt="Explain what this project does",
            options=ClaudeAgentOptions(max_turns=2),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so the follow-up query below still runs.
        print(f"Session ended with an error: {error}")

    # Compact the same conversation
    async for message in query(
        prompt="/compact",
        options=ClaudeAgentOptions(continue_conversation=True, max_turns=1),
    ):
        if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
            print("Compaction completed")
            print("Pre-compaction tokens:", message.data["compact_metadata"]["pre_tokens"])
            print("Trigger:", message.data["compact_metadata"]["trigger"])
            # Example output:
            # Compaction completed
            # Pre-compaction tokens: 1842
            # Trigger: manual


asyncio.run(main())
```

A `compact_boundary` message only arrives when compaction ran. When a continued session has messages but nothing `/compact` can summarize, the run still ends with a `success` result rather than an error, and no `compact_boundary` message arrives. The result text then carries the reason, for example `Not enough messages to compact.` when the session holds a prompt but no reply from Claude yet. A fresh one-shot `query()` call starts with empty context, so use this pattern in a session with prior turns, for example in [streaming input mode](agent-sdk-streaming-vs-single-mode.md) or when resuming a session.


[​](#reset-context-with-/clear)

Reset context with `/clear`

The `/clear` command resets the conversation to an empty context, so subsequent prompts start with no prior conversation history. The previous conversation remains on disk. You can return to that conversation by passing its session ID to the [`resume` option](agent-sdk-sessions.md#resume-by-id). `/clear` is useful in [streaming input mode](agent-sdk-streaming-vs-single-mode.md), where you send multiple prompts over a single connection. For one-shot `query()` calls, each call already starts with empty context, so sending `/clear` has no practical effect. Start a new `query()` instead.


[​](#create-skills)

Create skills

Create each skill as a directory containing a `SKILL.md` file with YAML frontmatter and Markdown content. The `description` field determines when Claude invokes your skill. **Example directory structure**:

```python
.claude/skills/security-check/
└── SKILL.md
```


[​](#choose-a-discovery-level)

Choose a discovery level

Save skills at either of the two most common [discovery levels](../08-Plugins-Skills/skills.md#where-skills-live):

- **Project skills**: `.claude/skills/`, available only in the current project
- **Personal skills**: `~/.claude/skills/`, available across all your projects

If you have existing custom command files in `.claude/commands/`, they keep working. A command file at `.claude/commands/deploy.md` creates `/deploy` and works the same way as a skill at `.claude/skills/deploy/SKILL.md` would. If a command file and a skill share a name, see [Resolve skills that share a name](../08-Plugins-Skills/skills.md#resolve-skills-that-share-a-name) for which one runs. The SDK loads `.claude/commands/` and `~/.claude/commands/` files from the same two scopes as skills. See [Extend Claude with skills](../08-Plugins-Skills/skills.md) for the complete guide to both artifact forms.


[​](#create-and-dispatch-your-first-skill)

Create and dispatch your first skill

To see the full flow, create `.claude/skills/security-check/SKILL.md`:

```python
---
name: security-check
description: Run a security vulnerability scan
---

Analyze the codebase for security vulnerabilities including:
- SQL injection risks
- XSS vulnerabilities
- Exposed credentials
- Insecure configurations
```

Once the file exists, the skill is available through the SDK. Claude invokes it when a request matches its description, and you can dispatch it directly:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "/security-check",
  options: { maxTurns: 10 }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    async for message in query(
        prompt="/security-check", options=ClaudeAgentOptions(max_turns=10)
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

A successful run ends with a `success` result whose text carries the scan findings. Against a small Express app with seeded issues, the result text begins:

```python
**Security scan of `app.js` — 4 findings (most severe first):**

1. **SQL Injection** (line 8) — `req.query.name` is concatenated directly into the SQL string. Trivially exploitable (`' OR '1'='1`, `'; DROP TABLE users;--`). **Fix:** use parameterized queries, e.g. `db.query("SELECT * FROM users WHERE name = ?", [req.query.name], cb)`.
...
```

The skill’s name also appears in the init message’s `slash_commands` array.

Claude Code includes bundled `code-review` and `verify` skills. If you name a `.claude/commands/` file after one of them, for example `.claude/commands/code-review.md`, the file’s command shadows the bundled skill and `slash_commands` lists the name once.


[​](#pre-approve-tools-for-skills)

Pre-approve tools for skills

In SDK sessions, you can pre-approve tools for a project or personal skill with the skill’s [`allowed-tools`](../08-Plugins-Skills/skills.md#pre-approve-tools-for-a-skill) frontmatter or with the `allowedTools` option (`allowed_tools` in Python) in your query configuration. If your organization sets [`allowManagedPermissionRulesOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly) in managed settings, Claude Code ignores both. Skills [synced from claude.ai](../08-Plugins-Skills/skills.md#how-claude-code-handles-the-frontmatter-of-a-synced-skill) follow their own frontmatter rules.

Skills run with the session’s tools. The example below pre-approves `Read`, `Grep`, and `Glob` with `allowedTools` (`allowed_tools` in Python), so Claude can inspect files while running the [security-check skill](#create-and-dispatch-your-first-skill) without stopping for approval:

Python

TypeScript

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    setting_sources=["user", "project"],  # Load skills from filesystem
    skills="all",
    allowed_tools=["Read", "Grep", "Glob"],
)


async def main():
    async for message in query(prompt="Check this project for security issues", options=options):
        print(message)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Check this project for security issues",
  options: {
    settingSources: ["user", "project"], // Load skills from filesystem
    skills: "all",
    allowedTools: ["Read", "Grep", "Glob"]
  }
})) {
  console.log(message);
}
```

In the stream, the skill invocation appears as a Skill tool use, followed by Read calls on the project files. The run ends with a `success` result whose text carries the findings. The list pre-approves the named tools rather than restricting the others. For the full permission flow, including permission modes and the `canUseTool` callback, see [Permissions](agent-sdk-permissions.md).


[​](#troubleshooting)

Troubleshooting


[​](#skills-not-found)

Skills not found

**Check settingSources configuration**: the SDK discovers skills through the `user` and `project` setting sources. If you set `settingSources`/`setting_sources` explicitly and omit those sources, the SDK doesn’t load skills:

Python

TypeScript

```python
# Skills not loaded: setting_sources excludes user and project
options = ClaudeAgentOptions(setting_sources=[], skills="all")

# Skills loaded: user and project sources included
options = ClaudeAgentOptions(
    setting_sources=["user", "project"],
    skills="all",
)
```

```python
// Skills not loaded: settingSources excludes user and project
const optionsWithoutSkills = {
  settingSources: [],
  skills: "all"
};

// Skills loaded: user and project sources included
const optionsWithSkills = {
  settingSources: ["user", "project"],
  skills: "all"
};
```

For which skill directories each source loads, see the [filesystem sources table](agent-sdk-claude-code-features.md#control-filesystem-settings-with-settingsources). For more details on `settingSources`/`setting_sources`, see the [TypeScript SDK reference](agent-sdk-typescript.md#settingsource) or [Python SDK reference](agent-sdk-python.md#settingsource). **Check working directory**: the SDK loads skills from `.claude/skills/` in the `cwd` option and in every parent directory up to the repository root. Ensure `cwd` points at or below the directory containing `.claude/skills/`, within the same repository:

Python

TypeScript

```python
# Ensure your cwd points to the directory containing .claude/skills/
options = ClaudeAgentOptions(
    cwd="/path/to/project",  # .claude/skills/ here or in a parent directory
    setting_sources=["user", "project"],  # Loads skills from these sources
    skills="all",
)
```

```python
// Ensure your cwd points to the directory containing .claude/skills/
const options = {
  cwd: "/path/to/project", // .claude/skills/ here or in a parent directory
  settingSources: ["user", "project"], // Loads skills from these sources
  skills: "all"
};
```

See [Use skills with the Agent SDK](#use-skills-with-the-agent-sdk) for the complete pattern. **Verify filesystem location**:

```python
# Check project skills
ls .claude/skills/*/SKILL.md

# Check personal skills
ls ~/.claude/skills/*/SKILL.md
```


[​](#skill-not-being-used)

Skill not being used

**Check the `skills` option**: if you passed a `skills` list, confirm the skill’s name is included. When Claude tries to invoke an unlisted skill, the Skill tool returns `Skill <name> is not in this session's skills allowlist`. Add the name to your list, or dispatch the skill directly by sending `/<name>` in a prompt, which works without listing. **Check the description**: ensure it’s specific and includes relevant keywords. See [Agent Skills best practices](../04-API-Reference/Agents-Tools/agents-and-tools-agent-skills-best-practices.md#writing-effective-descriptions) for guidance on writing effective descriptions.


[​](#invalid-skill-name-error)

Invalid skill name error

When a name in your `skills` list can’t work as an exact skill name, `query()` rejects the list before starting the Claude Code process. Names that trigger the rejection include:

- An empty name
- A name containing parentheses, commas, or control characters
- A name padded with whitespace
- A wildcard form such as a bare `*` or a `:*` suffix

Each SDK surfaces the rejection differently:

- TypeScript

- Python

The TypeScript SDK throws an `Error` stating the rule the entry broke. For example, `skills: ["docs:*"]` throws:

```python
Invalid skill name "docs:*": wildcard-suffix names are not allowed; list each skill by its exact name.
```

An empty name reports `Skill names must be non-empty strings.`Before TypeScript Agent SDK 0.3.221, the SDK didn’t run this check.

The Python SDK raises `ValueError` stating the rule the entry broke. For example, `skills=["docs:*"]` raises:

```python
ValueError: Invalid skill name 'docs:*': wildcard-suffix names are not allowed; list each skill by its exact name.
```

An empty name reports `Skill names must be non-empty strings`.Before Python Agent SDK 0.2.129, the SDK didn’t run this check.


[​](#additional-troubleshooting)

Additional troubleshooting

For general skills troubleshooting, such as YAML syntax errors and debugging, see the [Claude Code skills troubleshooting section](../08-Plugins-Skills/skills.md#troubleshooting).


[​](#next-steps)

Next steps

The [Claude Code skills guide](../08-Plugins-Skills/skills.md) covers authoring in depth. Its guidance applies to SDK sessions. Start with these sections:

- [Frontmatter reference](../08-Plugins-Skills/skills.md#frontmatter-reference): every supported field
- [Pass arguments to skills](../08-Plugins-Skills/skills.md#pass-arguments-to-skills): `$ARGUMENTS`, `$0`, `$1`, and skill stacking. The [full substitution table](../08-Plugins-Skills/skills.md#available-string-substitutions) adds named arguments and the `${CLAUDE_*}` variables
- [Inject dynamic context](../08-Plugins-Skills/skills.md#inject-dynamic-context): `` !`command` `` lines that run before Claude sees the skill content
- [Choose where skills load](../08-Plugins-Skills/skills.md#where-skills-live): every skill location, plugin namespacing, and which skill runs when two share a name


[​](#related-resources)

Related resources

- [Commands in Claude Code](../02-Claude-Code-CLI/commands.md): the full command surface, including every built-in
- [Agent Skills overview](../04-API-Reference/Agents-Tools/agents-and-tools-agent-skills-overview.md): conceptual overview, benefits, and architecture
- [Agent Skills best practices](../04-API-Reference/Agents-Tools/agents-and-tools-agent-skills-best-practices.md): authoring guidelines for effective skills
- [Agent Skills cookbook](https://platform.claude.com/cookbook/skills-notebooks-01-skills-introduction): example skills and templates
- [Subagents in the SDK](agent-sdk-subagents.md): similar filesystem-based agents with programmatic options
- [SDK overview](agent-sdk-overview.md): general SDK concepts
- [TypeScript SDK reference](agent-sdk-typescript.md): complete API documentation
- [Python SDK reference](agent-sdk-python.md): complete API documentation

---

## Slash Commands in the SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/slash-commands
Path: 05-Agent-SDK/agent-sdk-slash-commands.md

## On this page

- [Discovering Available Slash Commands](#discovering-available-slash-commands)
- [Sending Slash Commands](#sending-slash-commands)
- [Common Slash Commands](#common-slash-commands)
  - [/compact - Compact conversation history](#%2Fcompact-compact-conversation-history)
  - [/clear - Reset conversation context](#%2Fclear-reset-conversation-context)
- [Creating Custom Slash Commands](#creating-custom-slash-commands)
  - [File Locations](#file-locations)
  - [File Format](#file-format)
  - [Basic Example](#basic-example)
  - [With Frontmatter](#with-frontmatter)
  - [Using Custom Commands in the SDK](#using-custom-commands-in-the-sdk)
  - [Advanced Features](#advanced-features)
  - [Arguments and Placeholders](#arguments-and-placeholders)
  - [Bash Command Execution](#bash-command-execution)
  - [File References](#file-references)
  - [Organization with Namespacing](#organization-with-namespacing)
  - [Practical Examples](#practical-examples)
  - [Pull Request Review Command](#pull-request-review-command)
  - [Test Runner Command](#test-runner-command)
- [See Also](#see-also)

Customize behavior

# Slash Commands in the SDK

Copy pageCopy page

Learn how to use slash commands to control Claude Code sessions through the SDK

Copy pageCopy page

Slash commands provide a way to control Claude Code sessions with special commands that start with `/`. These commands can be sent through the SDK to perform actions like compacting context, listing context usage, or invoking custom commands. Only commands that work without an interactive terminal are dispatchable through the SDK; the `system/init` message lists the ones available in your session.


[​](#discovering-available-slash-commands)

Discovering Available Slash Commands

The Claude Agent SDK provides information about available slash commands in the system initialization message. Access this information when your session starts:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Hello Claude",
  options: { maxTurns: 1 }
})) {
  if (message.type === "system" && message.subtype === "init") {
    console.log("Available slash commands:", message.slash_commands);
    // Includes built-in commands plus bundled skills, for example:
    // ["clear", "compact", "context", "usage", "code-review", "verify", ...]
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage


async def main():
    async for message in query(prompt="Hello Claude", options=ClaudeAgentOptions(max_turns=1)):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            print("Available slash commands:", message.data["slash_commands"])
            # Includes built-in commands plus bundled skills, for example:
            # ["clear", "compact", "context", "usage", "code-review", "verify", ...]


asyncio.run(main())
```


[​](#sending-slash-commands)

Sending Slash Commands

Send slash commands by including them in your prompt string, just like regular text. Commands that act on conversation history, such as `/compact`, need prior messages to work with, so the examples below ask a question first and then send the command as a follow-up to the same conversation:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Build up conversation history first
try {
  for await (const message of query({
    prompt: "What does the README in this directory cover?",
    options: { maxTurns: 2 }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the follow-up query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Send a slash command as a follow-up to the same conversation
for await (const message of query({
  prompt: "/compact",
  options: { continue: true, maxTurns: 1 }
})) {
  if (message.type === "result") {
    console.log("Command executed, result subtype:", message.subtype);
    // Example output: Command executed, result subtype: success
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    # Build up conversation history first
    try:
        async for message in query(
            prompt="What does the README in this directory cover?",
            options=ClaudeAgentOptions(max_turns=2),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so the follow-up query below still runs.
        print(f"Session ended with an error: {error}")

    # Send a slash command as a follow-up to the same conversation
    async for message in query(
        prompt="/compact",
        options=ClaudeAgentOptions(continue_conversation=True, max_turns=1),
    ):
        if isinstance(message, ResultMessage):
            print("Command executed, result subtype:", message.subtype)
            # Example output: Command executed, result subtype: success


asyncio.run(main())
```

A query can end with an error result, for example when the `maxTurns` / `max_turns` limit is reached before the work completes. The final result message then has `is_error: true` and an error subtype such as `error_max_turns` instead of `success`.After yielding that final result message, the SDK raises an error, because the CLI process exits with a non-zero code.Wrap the loop in a `try`/`catch` in TypeScript or `try`/`except` in Python if your command might hit the limit, as shown in [Single Message Input](agent-sdk-streaming-vs-single-mode.md#single-message-input), or set `maxTurns` high enough for the work to complete. In Python, catch `Exception`: the SDK surfaces error results as a plain `Exception`.


[​](#common-slash-commands)

Common Slash Commands


[​](#/compact-compact-conversation-history)

`/compact` - Compact conversation history

The `/compact` command reduces the size of your conversation history by summarizing older messages while preserving important context. Compaction needs an existing conversation with at least two prior exchanges to summarize. This example has a conversation first, then compacts it and reads the `compact_boundary` system message that reports the result:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Compaction needs existing history, so have a conversation first
try {
  for await (const message of query({
    prompt: "Explain what this project does",
    options: { maxTurns: 2 }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the follow-up query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Compact the same conversation
for await (const message of query({
  prompt: "/compact",
  options: { continue: true, maxTurns: 1 }
})) {
  if (message.type === "system" && message.subtype === "compact_boundary") {
    console.log("Compaction completed");
    console.log("Pre-compaction tokens:", message.compact_metadata.pre_tokens);
    console.log("Trigger:", message.compact_metadata.trigger);
    // Example output:
    // Compaction completed
    // Pre-compaction tokens: 1842
    // Trigger: manual
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage


async def main():
    # Compaction needs existing history, so have a conversation first
    try:
        async for message in query(
            prompt="Explain what this project does",
            options=ClaudeAgentOptions(max_turns=2),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so the follow-up query below still runs.
        print(f"Session ended with an error: {error}")

    # Compact the same conversation
    async for message in query(
        prompt="/compact",
        options=ClaudeAgentOptions(continue_conversation=True, max_turns=1),
    ):
        if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
            print("Compaction completed")
            print("Pre-compaction tokens:", message.data["compact_metadata"]["pre_tokens"])
            print("Trigger:", message.data["compact_metadata"]["trigger"])
            # Example output:
            # Compaction completed
            # Pre-compaction tokens: 1842
            # Trigger: manual


asyncio.run(main())
```

A `compact_boundary` message only arrives when compaction ran. With nothing to summarize, `/compact` reports the reason instead of raising: the run still ends with a `success` result, no `compact_boundary` message is emitted, and the result text carries the message, for example `Not enough messages to compact.` after a single short exchange. A fresh one-shot `query()` call starts with empty context, so use this pattern in a session with prior turns, for example in [streaming input mode](agent-sdk-streaming-vs-single-mode.md) or when resuming a session.


[​](#/clear-reset-conversation-context)

`/clear` - Reset conversation context

The `/clear` command resets the conversation to an empty context, so subsequent prompts start with no prior conversation history. The previous conversation remains on disk and can be returned to by passing its session ID to the [`resume` option](agent-sdk-sessions.md#resume-by-id). This is useful in [streaming input mode](agent-sdk-streaming-vs-single-mode.md), where you send multiple prompts over a single connection. For one-shot `query()` calls, each call already starts with empty context, so sending `/clear` has no practical effect; start a new `query()` instead.


[​](#creating-custom-slash-commands)

Creating Custom Slash Commands

In addition to using built-in slash commands, you can create your own custom commands that are available through the SDK. You define custom commands as markdown files in specific directories, the same way you configure subagents.

Custom commands have been merged into skills. A file in `.claude/commands/` and a skill at `.claude/skills/<name>/SKILL.md` both create `/name` and work the same way. For new work, prefer skills, which add features like a directory for supporting files; see [Skills](agent-sdk-skills.md) for SDK usage. The CLI supports both locations, and the examples below remain accurate for `.claude/commands/`.


[​](#file-locations)

File Locations

Save custom slash commands in one of these directories, depending on their scope:

- **Project commands**: `.claude/commands/` - Available only in the current project. For new work, prefer `.claude/skills/`.
- **Personal commands**: `~/.claude/commands/` - Available across all your projects. For new work, prefer `~/.claude/skills/`.


[​](#file-format)

File Format

Each custom command is a markdown file where:

- The filename (without `.md` extension) becomes the command name
- The file content defines what the command does
- Optional YAML frontmatter provides configuration


[​](#basic-example)

Basic Example

Create the `.claude/commands` directory in your project if it doesn’t exist, then create `.claude/commands/refactor.md`:

```python
Refactor the selected code to improve readability and maintainability.
Focus on clean code principles and best practices.
```

This creates the `/refactor` command that you can use through the SDK.


[​](#with-frontmatter)

With Frontmatter

Create `.claude/commands/security-check.md`:

```python
---
allowed-tools: Read, Grep, Glob
description: Run security vulnerability scan
model: claude-opus-4-8
---

Analyze the codebase for security vulnerabilities including:
- SQL injection risks
- XSS vulnerabilities
- Exposed credentials
- Insecure configurations
```


[​](#using-custom-commands-in-the-sdk)

Using Custom Commands in the SDK

Once defined in the filesystem, custom commands are automatically available through the SDK:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Use a custom command
try {
  for await (const message of query({
    prompt: "/refactor src/auth/login.ts",
    options: { maxTurns: 3 }
  })) {
    if (message.type === "assistant") {
      console.log("Refactoring suggestions:", message.message);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the second query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Custom commands appear in the slash_commands list
for await (const message of query({
  prompt: "Hello",
  options: { maxTurns: 1 }
})) {
  if (message.type === "system" && message.subtype === "init") {
    console.log("Available commands:", message.slash_commands);
    // Includes built-in commands plus bundled skills and your custom commands, for example:
    // ["clear", "compact", "context", "usage", "code-review", "verify", "refactor", "security-check", ...]
  }
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, SystemMessage


async def main():
    # Use a custom command
    try:
        async for message in query(
            prompt="/refactor src/auth/login.py", options=ClaudeAgentOptions(max_turns=3)
        ):
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if hasattr(block, "text"):
                        print("Refactoring suggestions:", block.text)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so the second query below still runs.
        print(f"Session ended with an error: {error}")

    # Custom commands appear in the slash_commands list
    async for message in query(prompt="Hello", options=ClaudeAgentOptions(max_turns=1)):
        if isinstance(message, SystemMessage) and message.subtype == "init":
            print("Available commands:", message.data["slash_commands"])
            # Includes built-in commands plus bundled skills and your custom commands, for example:
            # ["clear", "compact", "context", "usage", "code-review", "verify", "refactor", "security-check", ...]


asyncio.run(main())
```


[​](#advanced-features)

Advanced Features


[​](#arguments-and-placeholders)

Arguments and Placeholders

Custom commands support dynamic arguments using placeholders: Create `.claude/commands/fix-issue.md`:

```python
---
argument-hint: [issue-number] [priority]
description: Fix a GitHub issue
---

Fix issue #$0 with priority $1.
Check the issue description and implement the necessary changes.
```

Use in SDK:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Pass arguments to custom command
try {
  for await (const message of query({
    prompt: "/fix-issue 123 high",
    options: { maxTurns: 5 }
  })) {
    // Command will process with $0="123" and $1="high"
    if (message.type === "result" && message.subtype === "success") {
      console.log("Issue fixed:", message.result);
    }
  }
} catch (err) {
  // The run ends with an error when it reaches the maxTurns limit
  console.error("Session ended with an error:", err);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    # Pass arguments to custom command
    try:
        async for message in query(prompt="/fix-issue 123 high", options=ClaudeAgentOptions(max_turns=5)):
            # Command will process with $0="123" and $1="high"
            if isinstance(message, ResultMessage):
                print("Issue fixed:", message.result)
    except Exception as error:
        # The run ends with an error when it reaches the max_turns limit
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

If the prompt passes fewer arguments than the placeholders reference, unmatched indexed placeholders such as `$1` stay in the command text verbatim. For the full substitution behavior, including named arguments, see [available string substitutions](../08-Plugins-Skills/skills.md#available-string-substitutions).


[​](#bash-command-execution)

Bash Command Execution

Custom commands can execute bash commands and include their output: Create `.claude/commands/git-commit.md`:

```python
---
allowed-tools: Bash(git add *), Bash(git status *), Bash(git commit *)
description: Create a git commit
---

## Context

- Current status: !`git status`
- Current diff: !`git diff HEAD`

## Task

Create a git commit with appropriate message based on the changes.
```


[​](#file-references)

File References

Include file contents using the `@` prefix: Create `.claude/commands/review-config.md`:

```python
---
description: Review configuration files
---

Review the following configuration files for issues:
- Package config: @package.json
- TypeScript config: @tsconfig.json
- Environment config: @.env

Check for security issues, outdated dependencies, and misconfigurations.
```


[​](#organization-with-namespacing)

Organization with Namespacing

Organize commands in subdirectories for better structure:

```python
.claude/commands/
├── frontend/
│   ├── component.md      # Creates /component (project:frontend)
│   └── style-check.md     # Creates /style-check (project:frontend)
├── backend/
│   ├── api-test.md        # Creates /api-test (project:backend)
│   └── db-migrate.md      # Creates /db-migrate (project:backend)
└── review.md              # Creates /review (project)
```

The subdirectory appears in the command description but doesn’t affect the command name itself.


[​](#practical-examples)

Practical Examples


[​](#pull-request-review-command)

Pull Request Review Command

Create `.claude/commands/review-pr.md`:

```python
---
allowed-tools: Read, Grep, Glob, Bash(git diff *)
description: Comprehensive code review
---

## Changed Files
!`git diff --name-only HEAD~1`

## Detailed Changes
!`git diff HEAD~1`

## Review Checklist

Review the above changes for:
1. Code quality and readability
2. Security vulnerabilities
3. Performance implications
4. Test coverage
5. Documentation completeness

Provide specific, actionable feedback organized by priority.
```

Claude Code includes bundled `code-review` and `verify` skills. If you name a custom command after one of them, for example `.claude/commands/code-review.md`, your command shadows the bundled skill and `slash_commands` lists the name once.


[​](#test-runner-command)

Test Runner Command

Create `.claude/commands/test.md`:

```python
---
allowed-tools: Bash, Read, Edit
argument-hint: [test-pattern]
description: Run tests with optional pattern
---

Run tests matching pattern: $ARGUMENTS

1. Detect the test framework (Jest, pytest, etc.)
2. Run tests with the provided pattern
3. If tests fail, analyze and fix them
4. Re-run to verify fixes
```


[​](#see-also)

See Also

- [Slash Commands](../08-Plugins-Skills/skills.md) - Complete slash command documentation
- [Subagents in the SDK](agent-sdk-subagents.md) - Similar filesystem-based configuration for subagents
- [TypeScript SDK reference](agent-sdk-typescript.md) - Complete API documentation
- [SDK overview](agent-sdk-overview.md) - General SDK concepts
- [CLI reference](../02-Claude-Code-CLI/cli-reference.md) - Command-line interface

---

## Stream responses in real-time - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/streaming-output
Path: 05-Agent-SDK/agent-sdk-streaming-output.md

## On this page

- [Enable streaming output](#enable-streaming-output)
- [StreamEvent reference](#streamevent-reference)
- [Message flow](#message-flow)
  - [Handle a stream that’s cut off](#handle-a-stream-that%E2%80%99s-cut-off)
- [Stream tool calls](#stream-tool-calls)
- [Build a streaming UI](#build-a-streaming-ui)
- [Known limitations](#known-limitations)
- [Next steps](#next-steps)

Input and output

# Stream responses in real-time

Copy pageCopy page

Get real-time responses from the Agent SDK as text and tool calls stream in

Copy pageCopy page

By default, the Agent SDK yields a complete `AssistantMessage` for each non-empty content block, such as a text block or a tool call, after Claude finishes generating that block. To receive incremental updates as text and tool calls are generated, enable partial message streaming.

This page covers output streaming (receiving tokens in real-time). For input modes (how you send messages), see [Send messages to agents](agent-sdk-streaming-vs-single-mode.md). You can also [stream responses using the Agent SDK via the CLI](../02-Claude-Code-CLI/headless.md).


[​](#enable-streaming-output)

Enable streaming output

To enable streaming, set `include_partial_messages` (Python) or `includePartialMessages` (TypeScript) to `true` in your options. This causes the SDK to yield `StreamEvent` messages containing raw API events as they arrive, in addition to the usual `AssistantMessage` and `ResultMessage`. Your code then needs to:

1.  Check each message’s type to distinguish `StreamEvent` from other message types
2.  For `StreamEvent`, extract the `event` field and check its `type`
3.  Look for `content_block_delta` events where `delta.type` is `text_delta`, which contain the actual text chunks

The example below enables streaming and prints text chunks as they arrive. Notice the nested type checks: first for `StreamEvent`, then for `content_block_delta`, then for `text_delta`:

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent
import asyncio


async def stream_response():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Bash", "Read"],
    )

    async for message in query(prompt="List the files in my project", options=options):
        if isinstance(message, StreamEvent):
            event = message.event
            if event.get("type") == "content_block_delta":
                delta = event.get("delta", {})
                if delta.get("type") == "text_delta":
                    print(delta.get("text", ""), end="", flush=True)


asyncio.run(stream_response())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List the files in my project",
  options: {
    includePartialMessages: true,
    allowedTools: ["Bash", "Read"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;
    if (event.type === "content_block_delta") {
      if (event.delta.type === "text_delta") {
        process.stdout.write(event.delta.text);
      }
    }
  }
}
```


[​](#streamevent-reference)

StreamEvent reference

When partial messages are enabled, you receive raw Claude API streaming events wrapped in an object. The type has different names in each SDK:

- **Python**: [`StreamEvent`](agent-sdk-python.md#streamevent) (import from `claude_agent_sdk.types`)
- **TypeScript**: [`SDKPartialAssistantMessage`](agent-sdk-typescript.md#sdkpartialassistantmessage) with `type: 'stream_event'`

Both contain raw Claude API events, not accumulated text. You need to extract and accumulate text deltas yourself. The `parent_tool_use_id` field is always `None` in Python and `null` in TypeScript. Stream events are emitted for the main session only; token-level deltas from subagents aren’t forwarded. To attribute output to a subagent, use complete messages, which carry `parent_tool_use_id`. See [Detect subagent invocation](agent-sdk-subagents.md#detect-subagent-invocation). Claude Code sets `user_message_uuid` on the turn’s first non-ping stream event, and again when the message the turn is answering changes, under the conditions in [`user_message_uuid`](agent-sdk-typescript.md#user_message_uuid). The Python `StreamEvent` doesn’t expose this field. The `event` field contains the raw streaming event from the [Claude API](../04-API-Reference/Guides/build-with-claude-streaming.md#event-types). Common event types include:

| Event Type            | Description                                     |
|:----------------------|:------------------------------------------------|
| `message_start`       | Start of a new message                          |
| `content_block_start` | Start of a new content block (text or tool use) |
| `content_block_delta` | Incremental update to content                   |
| `content_block_stop`  | End of a content block                          |
| `message_delta`       | Message-level updates (stop reason, usage)      |
| `message_stop`        | End of the message                              |


[​](#message-flow)

Message flow

Claude Code emits an `AssistantMessage` as each non-empty content block completes, so a response with a text block and a tool call yields two `AssistantMessage` objects. Each one carries only its own content block, and both share the same message ID, which you read as `message.message.id` in TypeScript and `message.message_id` in Python. With partial messages enabled, each `AssistantMessage` arrives before that block’s `content_block_stop` event, and you receive messages in this order:

```python
StreamEvent (message_start)
StreamEvent (content_block_start) - text block
StreamEvent (content_block_delta) - text chunks...
AssistantMessage - complete text block
StreamEvent (content_block_stop)
StreamEvent (content_block_start) - tool_use block
StreamEvent (content_block_delta) - tool input chunks...
AssistantMessage - complete tool_use block
StreamEvent (content_block_stop)
StreamEvent (message_delta)
StreamEvent (message_stop)
... tool executes ...
... more streaming events for next turn ...
ResultMessage - final result
```

Without partial messages enabled, you receive all message types except `StreamEvent`. Common types include `SystemMessage` (session initialization), `AssistantMessage` (complete content blocks), `ResultMessage` (final result), and a compact boundary message indicating when conversation history was compacted (`SDKCompactBoundaryMessage` in TypeScript; `SystemMessage` with subtype `"compact_boundary"` in Python).


[​](#handle-a-stream-that’s-cut-off)

Handle a stream that’s cut off

If a stream is cut off mid-message, such as when you interrupt the turn or the connection drops, you still receive that message’s `message_stop` before the turn ends. A cut text or thinking block also gets its `content_block_stop`. A cut tool call doesn’t, so if `message_stop` arrives while a tool call’s block is still open, treat that call’s input as incomplete. Before Claude Code v2.1.290, a cut stream could end the turn without `message_stop`, so a reply you render from stream events could stay shown as in progress. The TypeScript Agent SDK bundles Claude Code v2.1.290 or later from v0.3.290, and the Python Agent SDK from v0.2.164. If a reply stays shown as in progress after the turn ends, update the SDK.


[​](#stream-tool-calls)

Stream tool calls

Tool calls also stream incrementally. You can track when tools start, receive their input as it’s generated, and see when they complete. The example below tracks the current tool being called and accumulates the JSON input as it streams in. It uses three event types:

- `content_block_start`: tool begins
- `content_block_delta` with `input_json_delta`: input chunks arrive
- `content_block_stop`: tool call complete

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent
import asyncio


async def stream_tool_calls():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Read", "Bash"],
    )

    # Track the current tool and accumulate its input JSON
    current_tool = None
    tool_input = ""

    async for message in query(prompt="Read the README.md file", options=options):
        if isinstance(message, StreamEvent):
            event = message.event
            event_type = event.get("type")

            if event_type == "content_block_start":
                # New tool call is starting
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    current_tool = content_block.get("name")
                    tool_input = ""
                    print(f"Starting tool: {current_tool}")

            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                if delta.get("type") == "input_json_delta":
                    # Accumulate JSON input as it streams in
                    chunk = delta.get("partial_json", "")
                    tool_input += chunk
                    print(f"  Input chunk: {chunk}")

            elif event_type == "content_block_stop":
                # Tool call complete - show final input
                if current_tool:
                    print(f"Tool {current_tool} called with: {tool_input}")
                    current_tool = None


asyncio.run(stream_tool_calls())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Track the current tool and accumulate its input JSON
let currentTool: string | null = null;
let toolInput = "";

for await (const message of query({
  prompt: "Read the README.md file",
  options: {
    includePartialMessages: true,
    allowedTools: ["Read", "Bash"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;

    if (event.type === "content_block_start") {
      // New tool call is starting
      if (event.content_block.type === "tool_use") {
        currentTool = event.content_block.name;
        toolInput = "";
        console.log(`Starting tool: ${currentTool}`);
      }
    } else if (event.type === "content_block_delta") {
      if (event.delta.type === "input_json_delta") {
        // Accumulate JSON input as it streams in
        const chunk = event.delta.partial_json;
        toolInput += chunk;
        console.log(`  Input chunk: ${chunk}`);
      }
    } else if (event.type === "content_block_stop") {
      // Tool call complete - show final input
      if (currentTool) {
        console.log(`Tool ${currentTool} called with: ${toolInput}`);
        currentTool = null;
      }
    }
  }
}
```


[​](#build-a-streaming-ui)

Build a streaming UI

This example combines text and tool streaming into a cohesive UI. It tracks whether the agent is currently executing a tool (using an `in_tool` flag) to show status indicators like `[Using Read...]` while tools run. Text streams normally when not in a tool, and tool completion triggers a “done” message. This pattern is useful for chat interfaces that need to show progress during multi-step agent tasks.

Python

TypeScript

```python
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
from claude_agent_sdk.types import StreamEvent
import asyncio
import sys


async def streaming_ui():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Read", "Bash", "Grep"],
    )

    # Track whether we're currently in a tool call
    in_tool = False

    async for message in query(
        prompt="Find all TODO comments in the codebase", options=options
    ):
        if isinstance(message, StreamEvent):
            event = message.event
            event_type = event.get("type")

            if event_type == "content_block_start":
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    # Tool call is starting - show status indicator
                    tool_name = content_block.get("name")
                    print(f"\n[Using {tool_name}...]", end="", flush=True)
                    in_tool = True

            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                # Only stream text when not executing a tool
                if delta.get("type") == "text_delta" and not in_tool:
                    sys.stdout.write(delta.get("text", ""))
                    sys.stdout.flush()

            elif event_type == "content_block_stop":
                if in_tool:
                    # Tool call finished
                    print(" done", flush=True)
                    in_tool = False

        elif isinstance(message, ResultMessage):
            # Agent finished all work
            print(f"\n\n--- Complete ---")


asyncio.run(streaming_ui())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Track whether we're currently in a tool call
let inTool = false;

for await (const message of query({
  prompt: "Find all TODO comments in the codebase",
  options: {
    includePartialMessages: true,
    allowedTools: ["Read", "Bash", "Grep"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;

    if (event.type === "content_block_start") {
      if (event.content_block.type === "tool_use") {
        // Tool call is starting - show status indicator
        process.stdout.write(`\n[Using ${event.content_block.name}...]`);
        inTool = true;
      }
    } else if (event.type === "content_block_delta") {
      // Only stream text when not executing a tool
      if (event.delta.type === "text_delta" && !inTool) {
        process.stdout.write(event.delta.text);
      }
    } else if (event.type === "content_block_stop") {
      if (inTool) {
        // Tool call finished
        console.log(" done");
        inTool = false;
      }
    }
  } else if (message.type === "result") {
    // Agent finished all work
    console.log("\n\n--- Complete ---");
  }
}
```


[​](#known-limitations)

Known limitations

- **Structured output**: with partial messages enabled, the JSON streams as a tool call’s unvalidated `input_json_delta` chunks, and only the validated result reaches the final `ResultMessage.structured_output`. See [structured outputs](agent-sdk-structured-outputs.md) for details.


[​](#next-steps)

Next steps

Now that you can stream text and tool calls in real-time, explore these related topics:

- [Interactive vs one-shot queries](agent-sdk-streaming-vs-single-mode.md): choose between input modes for your use case
- [Structured outputs](agent-sdk-structured-outputs.md): get typed JSON responses from the agent
- [Permissions](agent-sdk-permissions.md): control which tools the agent can use

---

## Streaming Input - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/streaming-vs-single-mode
Path: 05-Agent-SDK/agent-sdk-streaming-vs-single-mode.md

## On this page

- [Overview](#overview)
- [Streaming Input Mode (Recommended)](#streaming-input-mode-recommended)
  - [Benefits](#benefits)
  - [Implementation Example](#implementation-example)
- [Single Message Input](#single-message-input)
  - [When to Use Single Message Input](#when-to-use-single-message-input)
  - [Limitations](#limitations)
  - [Implementation Example](#implementation-example-2)

Input and output

# Streaming Input

Copy pageCopy page

Understanding the two input modes for Claude Agent SDK and when to use each

Copy pageCopy page


[​](#overview)

Overview

The Claude Agent SDK supports two distinct input modes for interacting with agents:

- **Streaming Input Mode**: a persistent, interactive session
- **Single Message Input**: one-shot queries that use session state and resuming


[​](#streaming-input-mode-recommended)

Streaming Input Mode (Recommended)

Streaming input mode is the **preferred** way to use the Claude Agent SDK. It provides full access to the agent’s capabilities and enables rich, interactive experiences. It allows the agent to operate as a long lived process that takes in user input, handles interruptions, surfaces permission requests, and handles session management.


[​](#benefits)

Benefits

In streaming input mode, you work in a persistent session with these capabilities:

- **Image uploads**: attach images directly to messages for visual analysis and understanding
- **Queued messages**: send multiple messages that process sequentially, with ability to interrupt
- **Tool integration**: full access to all tools and custom MCP servers during the session
- **Real-time feedback**: see responses as they’re generated, not just final results
- **Context persistence**: maintain conversation context across multiple turns naturally


[​](#implementation-example)

Implementation Example

These examples read an image named `diagram.png` from the working directory. Create one there first, or change the filename to point at your own image.

TypeScript

Python

```python
import { query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
import { readFile } from "fs/promises";

async function* generateMessages(): AsyncGenerator<SDKUserMessage> {
  // First message
  yield {
    type: "user",
    message: {
      role: "user",
      content: "Analyze this codebase for security issues"
    },
    parent_tool_use_id: null
  };

  // Wait for conditions or user input
  await new Promise((resolve) => setTimeout(resolve, 2000));

  // Follow-up with image
  yield {
    type: "user",
    message: {
      role: "user",
      content: [
        {
          type: "text",
          text: "Review this architecture diagram"
        },
        {
          type: "image",
          source: {
            type: "base64",
            media_type: "image/png",
            data: await readFile("diagram.png", "base64")
          }
        }
      ]
    },
    parent_tool_use_id: null
  };
}

// Process streaming responses
for await (const message of query({
  prompt: generateMessages(),
  options: {
    maxTurns: 10,
    allowedTools: ["Read", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}
```

```python
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    TextBlock,
)
import asyncio
import base64


async def streaming_analysis():
    async def message_generator():
        # First message
        yield {
            "type": "user",
            "message": {
                "role": "user",
                "content": "Analyze this codebase for security issues",
            },
        }

        # Wait for conditions
        await asyncio.sleep(2)

        # Follow-up with image
        with open("diagram.png", "rb") as f:
            image_data = base64.b64encode(f.read()).decode()

        yield {
            "type": "user",
            "message": {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Review this architecture diagram"},
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": "image/png",
                            "data": image_data,
                        },
                    },
                ],
            },
        }

    # Use ClaudeSDKClient for streaming input
    options = ClaudeAgentOptions(max_turns=10, allowed_tools=["Read", "Grep"])

    async with ClaudeSDKClient(options) as client:
        # Send streaming input
        await client.query(message_generator())

        # Process responses
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(block.text)


asyncio.run(streaming_analysis())
```

When you run the example, the TypeScript version prints each response as it completes. The Python version’s `receive_response()` loop ends at the first result message, so it prints the security analysis; to read both responses, use one `query()` and `receive_response()` pair per message as shown in the [Python reference’s example of continuing a conversation](agent-sdk-python.md#example-continuing-a-conversation). If an image block’s `source` is missing or isn’t an object, the SDK doesn’t report an error. Claude Code sends Claude a text note in place of the image, such as `[Image could not be processed: image block has no source object]`, and the session continues.

In the TypeScript SDK, if your message generator throws, for example when a file it reads is missing, the stream ends with an error that reads `Claude Code process aborted by user` instead of the original error, so check the code inside your generator first when you see that message. The error may also be preceded by a long minified line of bundled SDK source, so read to the end of the output for the error text.In the Python SDK, a generator exception is logged at debug level and the session stalls without raising, so if a streaming session hangs with no output, enable debug logging and check your generator.


[​](#single-message-input)

Single Message Input

Single message input is simpler but more limited.


[​](#when-to-use-single-message-input)

When to Use Single Message Input

Use single message input when:

- You need a one-shot response
- You do not need image attachments or mid-session control methods
- You need to operate in a stateless environment, such as a lambda function


[​](#limitations)

Limitations

Single message input mode does **not** support:

- Direct image attachments in messages
- Dynamic message queueing
- Real-time interruption
- Natural multi-turn conversations

If a query ends with an error result, such as `error_max_turns`, a single message `query()` call raises an error that includes the failure text after yielding the final result message, so wrap the loop in a try block if your code needs to continue. See [Handle the result](agent-sdk-agent-loop.md#handle-the-result) for the result subtypes.


[​](#implementation-example-2)

Implementation Example

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Simple one-shot query
// query() throws after an error result, such as error_max_turns
try {
  for await (const message of query({
    prompt: "Explain the authentication flow",
    options: {
      maxTurns: 5,
      allowedTools: ["Read", "Grep"]
    }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  console.error(`Query failed: ${error}`);
}

// Continue conversation with session management
try {
  for await (const message of query({
    prompt: "Now explain the authorization process",
    options: {
      continue: true,
      maxTurns: 5
    }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  console.error(`Query failed: ${error}`);
}
```

```python
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
import asyncio


async def single_message_example():
    # Simple one-shot query using query() function
    # query() raises ResultError after an error result, such as error_max_turns
    try:
        async for message in query(
            prompt="Explain the authentication flow",
            options=ClaudeAgentOptions(max_turns=5, allowed_tools=["Read", "Grep"]),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as e:
        print(f"Query failed: {e}")

    # Continue conversation with session management
    try:
        async for message in query(
            prompt="Now explain the authorization process",
            options=ClaudeAgentOptions(continue_conversation=True, max_turns=5),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as e:
        print(f"Query failed: {e}")


asyncio.run(single_message_example())
```

When you run the example, each query prints its final result text: first the authentication explanation, then the authorization explanation.

---

## Get structured output from agents - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/structured-outputs
Path: 05-Agent-SDK/agent-sdk-structured-outputs.md

## On this page

- [Why structured outputs?](#why-structured-outputs)
- [Quick start](#quick-start)
- [Type-safe schemas with Zod and Pydantic](#type-safe-schemas-with-zod-and-pydantic)
- [Output format configuration](#output-format-configuration)
- [Example: TODO tracking agent](#example-todo-tracking-agent)
- [Error handling](#error-handling)
- [Related resources](#related-resources)

Input and output

# Get structured output from agents

Copy pageCopy page

Return validated JSON from agent workflows using JSON Schema, Zod, or Pydantic. Get type-safe, structured data after multi-turn tool use.

Copy pageCopy page

Structured outputs let you define the exact shape of data you want back from an agent. The agent can use any tools it needs to complete the task, and you still get validated JSON matching your schema at the end. Define a [JSON Schema](https://json-schema.org/understanding-json-schema/about) for the structure you need, and the SDK validates the output against it, re-prompting on mismatch. If validation does not succeed within the retry limit, the result is an error instead of structured data; see [Error handling](#error-handling). For full type safety, use [Zod](#type-safe-schemas-with-zod-and-pydantic) (TypeScript) or [Pydantic](#type-safe-schemas-with-zod-and-pydantic) (Python) to define your schema and get strongly typed objects back.


[​](#why-structured-outputs)

Why structured outputs?

Agents return free-form text by default, which works for chat but not when you need to use the output programmatically. Structured outputs give you typed data you can pass directly to your application logic, database, or UI components. Consider a recipe app where an agent searches the web and brings back recipes. Without structured outputs, you get free-form text that you’d need to parse yourself. With structured outputs, you define the shape you want and get typed data you can use directly in your app.

Without structured outputs

```python
Here's a classic chocolate chip cookie recipe!

**Chocolate Chip Cookies**
Prep time: 15 minutes | Cook time: 10 minutes

Ingredients:
- 2 1/4 cups all-purpose flour
- 1 cup butter, softened
...
```

To use this in your app, you’d need to parse out the title, convert “15 minutes” to a number, separate ingredients from instructions, and handle inconsistent formatting across responses.

With structured outputs

```python
{
  "name": "Chocolate Chip Cookies",
  "prep_time_minutes": 15,
  "cook_time_minutes": 10,
  "ingredients": [
    { "item": "all-purpose flour", "amount": 2.25, "unit": "cups" },
    { "item": "butter, softened", "amount": 1, "unit": "cup" }
    // ...
  ],
  "steps": ["Preheat oven to 375°F", "Cream butter and sugar" /* ... */]
}
```

Typed data you can use directly in your UI.


[​](#quick-start)

Quick start

To use structured outputs, define a [JSON Schema](https://json-schema.org/understanding-json-schema/about) describing the shape of data you want, then pass it to `query()` via the `outputFormat` option (TypeScript) or `output_format` option (Python). When the agent finishes, the result message includes a `structured_output` field with validated data matching your schema. Before running the examples on this page, install the Claude Agent SDK by following the [quickstart](agent-sdk-quickstart.md#setup). The example below asks the agent to research Anthropic and return the company name, year founded, and headquarters as structured output.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Define the shape of data you want back
const schema = {
  type: "object",
  properties: {
    company_name: { type: "string" },
    founded_year: { type: "number" },
    headquarters: { type: "string" }
  },
  required: ["company_name"]
};

try {
  for await (const message of query({
    prompt: "Research Anthropic and provide key company information",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: schema
      }
    }
  })) {
    // The result message contains structured_output with validated data
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      console.log(message.structured_output);
      // { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, such as
  // error_max_structured_output_retries; see the Error handling section.
  console.error(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Define the shape of data you want back
schema = {
    "type": "object",
    "properties": {
        "company_name": {"type": "string"},
        "founded_year": {"type": "number"},
        "headquarters": {"type": "string"},
    },
    "required": ["company_name"],
}


async def main():
    try:
        async for message in query(
            prompt="Research Anthropic and provide key company information",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": schema}
            ),
        ):
            # The result message contains structured_output with validated data
            if isinstance(message, ResultMessage) and message.structured_output:
                print(message.structured_output)
                # {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
    except Exception as error:
        # A single-shot query() raises after yielding an error result, such as
        # error_max_structured_output_retries; see the Error handling section.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```


[​](#type-safe-schemas-with-zod-and-pydantic)

Type-safe schemas with Zod and Pydantic

Instead of writing JSON Schema by hand, you can use [Zod](https://zod.dev/) (TypeScript) or [Pydantic](https://docs.pydantic.dev/latest/) (Python) to define your schema. These libraries generate the JSON Schema for you and let you parse the response into a fully-typed object you can use throughout your codebase with autocomplete and type checking. The example below defines a schema for a feature implementation plan with a summary, list of steps (each with complexity level), and potential risks. The agent plans the feature and returns a typed `FeaturePlan` object. You can then access properties like `plan.summary` and iterate over `plan.steps` with full type safety. The SDK validates schemas with JSON Schema draft-07, so schemas that declare a newer version are rejected. Zod targets draft 2020-12 by default, so pass `target: "draft-7"` when converting your schema.

TypeScript

Python

```python
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";

// Define schema with Zod
const FeaturePlan = z.object({
  feature_name: z.string(),
  summary: z.string(),
  steps: z.array(
    z.object({
      step_number: z.number(),
      description: z.string(),
      estimated_complexity: z.enum(["low", "medium", "high"])
    })
  ),
  risks: z.array(z.string())
});

type FeaturePlan = z.infer<typeof FeaturePlan>;

// Convert to JSON Schema using the draft-07 target the SDK expects
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });

// Use in query
try {
  for await (const message of query({
    prompt:
      "Plan how to add dark mode support to a React app. Break it into implementation steps.",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: schema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      // Validate and get fully typed result
      const parsed = FeaturePlan.safeParse(message.structured_output);
      if (parsed.success) {
        const plan: FeaturePlan = parsed.data;
        console.log(`Feature: ${plan.feature_name}`);
        console.log(`Summary: ${plan.summary}`);
        plan.steps.forEach((step) => {
          console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
        });
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, such as
  // error_max_structured_output_retries; see the Error handling section.
  console.error(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from pydantic import BaseModel
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


class Step(BaseModel):
    step_number: int
    description: str
    estimated_complexity: str  # 'low', 'medium', 'high'


class FeaturePlan(BaseModel):
    feature_name: str
    summary: str
    steps: list[Step]
    risks: list[str]


async def main():
    try:
        async for message in query(
            prompt="Plan how to add dark mode support to a React app. Break it into implementation steps.",
            options=ClaudeAgentOptions(
                output_format={
                    "type": "json_schema",
                    "schema": FeaturePlan.model_json_schema(),
                }
            ),
        ):
            if isinstance(message, ResultMessage) and message.structured_output:
                # Validate and get fully typed result
                plan = FeaturePlan.model_validate(message.structured_output)
                print(f"Feature: {plan.feature_name}")
                print(f"Summary: {plan.summary}")
                for step in plan.steps:
                    print(
                        f"{step.step_number}. [{step.estimated_complexity}] {step.description}"
                    )
    except Exception as error:
        # A single-shot query() raises after yielding an error result, such as
        # error_max_structured_output_retries; see the Error handling section.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```


[​](#output-format-configuration)

Output format configuration

The `outputFormat` (TypeScript) or `output_format` (Python) option accepts an object with:

- `type`: Set to `"json_schema"` for structured outputs
- `schema`: A [JSON Schema](https://json-schema.org/understanding-json-schema/about) object defining your output structure. You can generate this from a Zod schema with `z.toJSONSchema(schema, { target: "draft-7" })` or a Pydantic model with `.model_json_schema()`

The SDK supports standard JSON Schema features including all basic types (object, array, string, number, boolean, null), `enum`, `const`, `required`, nested objects, and `$ref` definitions. For the full list of supported features and limitations, see [JSON Schema limitations](../04-API-Reference/Guides/build-with-claude-structured-outputs.md#json-schema-limitations). A schema that isn’t valid JSON Schema fails the run at startup with an error naming the problem. Before v2.1.205, an invalid schema was silently ignored and the agent returned unstructured text. The `format` keyword, such as `"format": "email"`, is accepted as an annotation and isn’t enforced by the SDK’s validator. Before v2.1.205, any schema containing `format` was treated as invalid.


[​](#example-todo-tracking-agent)

Example: TODO tracking agent

This example demonstrates how structured outputs work with multi-step tool use. The agent needs to find TODO comments in the codebase, then look up git blame information for each one. It autonomously decides which tools to use (Grep to search, Bash to run git commands) and combines the results into a single structured response. The schema includes optional fields (`author` and `date`) since git blame information might not be available for all files. The agent fills in what it can find and omits the rest.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Define structure for TODO extraction
const todoSchema = {
  type: "object",
  properties: {
    todos: {
      type: "array",
      items: {
        type: "object",
        properties: {
          text: { type: "string" },
          file: { type: "string" },
          line: { type: "number" },
          author: { type: "string" },
          date: { type: "string" }
        },
        required: ["text", "file", "line"]
      }
    },
    total_count: { type: "number" }
  },
  required: ["todos", "total_count"]
};

// Agent uses Grep to find TODOs, Bash to get git blame info
try {
  for await (const message of query({
    prompt: "Find all TODO comments in this codebase and identify who added them",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: todoSchema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      const data = message.structured_output as { total_count: number; todos: Array<{ file: string; line: number; text: string; author?: string; date?: string }> };
      console.log(`Found ${data.total_count} TODOs`);
      data.todos.forEach((todo) => {
        console.log(`${todo.file}:${todo.line} - ${todo.text}`);
        if (todo.author) {
          console.log(`  Added by ${todo.author} on ${todo.date}`);
        }
      });
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, such as
  // error_max_structured_output_retries; see the Error handling section.
  console.error(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Define structure for TODO extraction
todo_schema = {
    "type": "object",
    "properties": {
        "todos": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "text": {"type": "string"},
                    "file": {"type": "string"},
                    "line": {"type": "number"},
                    "author": {"type": "string"},
                    "date": {"type": "string"},
                },
                "required": ["text", "file", "line"],
            },
        },
        "total_count": {"type": "number"},
    },
    "required": ["todos", "total_count"],
}


async def main():
    # Agent uses Grep to find TODOs, Bash to get git blame info
    try:
        async for message in query(
            prompt="Find all TODO comments in this codebase and identify who added them",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": todo_schema}
            ),
        ):
            if isinstance(message, ResultMessage) and message.structured_output:
                data = message.structured_output
                print(f"Found {data['total_count']} TODOs")
                for todo in data["todos"]:
                    print(f"{todo['file']}:{todo['line']} - {todo['text']}")
                    if "author" in todo:
                        print(f"  Added by {todo['author']} on {todo['date']}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result, such as
        # error_max_structured_output_retries; see the Error handling section.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```


[​](#error-handling)

Error handling

Structured output generation can fail when the agent cannot produce valid JSON matching your schema. This typically happens when the schema is too complex for the task, the task itself is ambiguous, or the agent hits its retry limit trying to fix validation errors. It can also happen without any validation failure: a [model fallback](../02-Claude-Code-CLI/model-config.md#automatic-model-fallback) can retract an already-completed output mid-stream, and if no retry replaces it the run ends with the same error. Check the `errors` list on the error result message to tell the two causes apart before debugging your schema. When an error occurs, the result message has a `subtype` indicating what went wrong:

| Subtype                               | Meaning                                                                                                                         |
|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| `success`                             | Output was generated and validated successfully                                                                                 |
| `error_max_structured_output_retries` | No valid output remained after multiple attempts (validation failures, or a model-fallback retraction with no successful retry) |

A result can also end with subtype `success` but no `structured_output` value, for example when the run completes without the agent producing a structured output. Treat that case as a failure as well. The troubleshooting entry [structured_output is None but the result says success](agent-sdk-troubleshooting.md#structured_output-is-none-but-the-result-says-success) covers this case. The example below treats a result as successful only when the `subtype` is `success` and `structured_output` is present, and handles every other result as a failure:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const contactSchema = {
  type: "object",
  properties: {
    name: { type: "string" },
    email: { type: "string" }
  },
  required: ["name"]
};

try {
  for await (const msg of query({
    prompt: "Extract contact info from the document",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: contactSchema
      }
    }
  })) {
    if (msg.type === "result") {
      if (msg.subtype === "success" && msg.structured_output) {
        // Use the validated output
        console.log(msg.structured_output);
      } else if (msg.subtype === "error_max_structured_output_retries") {
        console.error("Could not produce valid output");
      } else {
        console.error("Run ended without a structured output");
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branches above have
  // already run; connection or process failures yield no result message.
  console.log(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

contact_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "email": {"type": "string"},
    },
    "required": ["name"],
}


async def main():
    try:
        async for message in query(
            prompt="Extract contact info from the document",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": contact_schema}
            ),
        ):
            if isinstance(message, ResultMessage):
                if message.subtype == "success" and message.structured_output:
                    # Use the validated output
                    print(message.structured_output)
                elif message.subtype == "error_max_structured_output_retries":
                    print("Could not produce valid output")
                else:
                    print("Run ended without a structured output")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branches above have
        # already run; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

**Tips for avoiding errors:**

- **Keep schemas focused.** Deeply nested schemas with many required fields are harder to satisfy. Start simple and add complexity as needed.
- **Match schema to task.** If the task might not have all the information your schema requires, make those fields optional.
- **Use clear prompts.** Ambiguous prompts make it harder for the agent to know what output to produce.


[​](#related-resources)

Related resources

- [JSON Schema documentation](https://json-schema.org/): learn JSON Schema syntax for defining complex schemas with nested objects, arrays, enums, and validation constraints
- [API Structured Outputs](../04-API-Reference/Guides/build-with-claude-structured-outputs.md): use structured outputs with the Claude API directly for single-turn requests without tool use
- [Custom tools](agent-sdk-custom-tools.md): give your agent custom tools to call during execution before returning structured output

---

## Subagents in the SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/subagents
Path: 05-Agent-SDK/agent-sdk-subagents.md

## On this page

- [Overview](#overview)
- [Benefits of using subagents](#benefits-of-using-subagents)
- [Create subagents](#create-subagents)
  - [Programmatic definition (recommended)](#programmatic-definition-recommended)
  - [AgentDefinition configuration](#agentdefinition-configuration)
  - [Filesystem-based definition (alternative)](#filesystem-based-definition-alternative)
- [What subagents inherit](#what-subagents-inherit)
- [Invoke subagents](#invoke-subagents)
  - [Automatic invocation](#automatic-invocation)
  - [Explicit invocation](#explicit-invocation)
  - [Dynamic agent configuration](#dynamic-agent-configuration)
- [Detect subagent invocation](#detect-subagent-invocation)
- [Resume subagents](#resume-subagents)
- [Tool restrictions](#tool-restrictions)
  - [Common tool combinations](#common-tool-combinations)
- [Cap subagent depth, concurrency, and spend](#cap-subagent-depth-concurrency-and-spend)
  - [Run Opus 5 with subagents](#run-opus-5-with-subagents)
- [Scale up with dynamic workflows](#scale-up-with-dynamic-workflows)
- [Troubleshooting](#troubleshooting)
  - [Claude not delegating to subagents](#claude-not-delegating-to-subagents)
  - [Filesystem-based agents not loading](#filesystem-based-agents-not-loading)
- [Related documentation](#related-documentation)

Extend with tools

# Subagents in the SDK

Copy pageCopy page

Define and invoke subagents to isolate context, run tasks in parallel, and apply specialized instructions in your Claude Agent SDK applications.

Copy pageCopy page

Subagents are separate agent instances that your main agent can spawn to handle focused subtasks. Use them to isolate context, run multiple analyses in parallel, and apply specialized instructions without adding to the main agent’s prompt.


[​](#overview)

Overview

You can create subagents in three ways:

- **Programmatically**: use the `agents` parameter in your `query()` options. See the [TypeScript](agent-sdk-typescript.md#agentdefinition) and [Python](agent-sdk-python.md#agentdefinition) references
- **Filesystem-based**: define agents as markdown files in `.claude/agents/` directories. See [defining subagents as files](../09-Agents-Patterns/sub-agents.md)
- **Built-in general-purpose**: Claude can invoke the built-in `general-purpose` subagent at any time via the Agent tool without you defining anything

This guide focuses on the programmatic approach, which is recommended for SDK applications.


[​](#benefits-of-using-subagents)

Benefits of using subagents

Because subagents are separate agent instances, delegating work to them gives you four benefits:

- **Context isolation**: each subagent runs in its own conversation, which starts fresh unless the subagent is a [fork](../09-Agents-Patterns/sub-agents.md#fork-the-current-conversation). Either way, intermediate tool calls and results stay inside the subagent; only its final message returns to the parent. A `research-assistant` subagent can explore dozens of files without any of that content accumulating in the main conversation. The parent receives a concise summary, not every file the subagent read. See [What subagents inherit](#what-subagents-inherit) for exactly what’s in the subagent’s context.
- **Parallelization**: multiple subagents can run concurrently, so independent subtasks finish in the time of the slowest one rather than the sum of all of them. During a code review, you can run `style-checker`, `security-scanner`, and `test-coverage` subagents simultaneously instead of sequentially.
- **Specialized instructions and knowledge**: each subagent can have a tailored system prompt with specific expertise, best practices, and constraints. A `database-migration` subagent can have detailed knowledge about SQL best practices, rollback strategies, and data integrity checks that would be unnecessary noise in the main agent’s instructions.
- **Tool restrictions**: subagents can be limited to specific tools, reducing the risk of unintended actions. A `doc-reviewer` subagent might only have access to Read and Grep tools, ensuring it can analyze but never accidentally modify your documentation files.


[​](#create-subagents)

Create subagents


[​](#programmatic-definition-recommended)

Programmatic definition (recommended)

Define subagents directly in your code using the `agents` parameter. Claude invokes subagents through the `Agent` tool. Most examples on this page print only the final result. To confirm that Claude delegated to a subagent rather than answering directly, see [Detect subagent invocation](#detect-subagent-invocation). This example creates two subagents: a code reviewer with read-only access and a test runner that can execute commands.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition


async def main():
    async for message in query(
        prompt="Review the authentication module for security issues",
        options=ClaudeAgentOptions(
            # Auto-approve these tools
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "code-reviewer": AgentDefinition(
                    # description tells Claude when to use this subagent
                    description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
                    # prompt defines the subagent's behavior and expertise
                    prompt="""You are a code review specialist with expertise in security, performance, and best practices.

When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements

Be thorough but concise in your feedback.""",
                    # tools restricts what the subagent can do (read-only here)
                    tools=["Read", "Grep", "Glob"],
                    # model overrides the default model for this subagent
                    model="sonnet",
                ),
                "test-runner": AgentDefinition(
                    description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
                    prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.

Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures""",
                    # Bash access lets this subagent run test commands
                    tools=["Bash", "Read", "Grep"],
                ),
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Review the authentication module for security issues",
  options: {
    // Auto-approve these tools
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      "code-reviewer": {
        // description tells Claude when to use this subagent
        description:
          "Expert code review specialist. Use for quality, security, and maintainability reviews.",
        // prompt defines the subagent's behavior and expertise
        prompt: `You are a code review specialist with expertise in security, performance, and best practices.

When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements

Be thorough but concise in your feedback.`,
        // tools restricts what the subagent can do (read-only here)
        tools: ["Read", "Grep", "Glob"],
        // model overrides the default model for this subagent
        model: "sonnet"
      },
      "test-runner": {
        description:
          "Runs and analyzes test suites. Use for test execution and coverage analysis.",
        prompt: `You are a test execution specialist. Run tests and provide clear analysis of results.

Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures`,
        // Bash access lets this subagent run test commands
        tools: ["Bash", "Read", "Grep"]
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}
```


[​](#agentdefinition-configuration)

AgentDefinition configuration

| Field             | Type                                                   | Required | Description                                                                                                                                                                                                                                                                                                                                     |
|:------------------|:-------------------------------------------------------|:---------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `description`     | `string`                                               | Yes      | Natural language description of when to use this agent                                                                                                                                                                                                                                                                                          |
| `prompt`          | `string`                                               | Yes      | The agent’s system prompt defining its role and behavior                                                                                                                                                                                                                                                                                        |
| `tools`           | `string[]`                                             | No       | Array of allowed tool names. If omitted, inherits every [tool available to subagents](../09-Agents-Patterns/sub-agents.md#available-tools)                                                                                                                                                                                                                      |
| `disallowedTools` | `string[]`                                             | No       | Array of tool names to remove from the agent’s tool set. MCP server-level patterns are also accepted: `mcp__server` or `mcp__server__*` removes every tool from that server, and `mcp__*` removes every MCP tool from any server                                                                                                                |
| `model`           | `string`                                               | No       | Model override for this agent. Accepts an alias such as `'fable'`, `'opus'`, `'sonnet'`, `'haiku'`, `'inherit'`, or a full model ID. `'inherit'` uses the main model. When you omit it, Claude Code picks the model in the [subagent model order](../09-Agents-Patterns/sub-agents.md#choose-a-model)                                                           |
| `skills`          | `string[]`                                             | No       | List of skill names to preload into the agent’s context at startup. Unlisted skills remain invocable through the Skill tool                                                                                                                                                                                                                     |
| `memory`          | `'user' | 'project' | 'local'`                         | No       | Memory source for this agent                                                                                                                                                                                                                                                                                                                    |
| `mcpServers`      | `(string | object)[]`                                  | No       | MCP servers available to this agent, by name or inline config                                                                                                                                                                                                                                                                                   |
| `initialPrompt`   | `string`                                               | No       | Auto-submitted as the first user turn when this agent runs as the main thread agent. Ignored when the agent is invoked as a subagent                                                                                                                                                                                                            |
| `maxTurns`        | `number`                                               | No       | Maximum number of agentic turns before the agent stops. When the agent reaches the limit, Claude Code returns its output marked as partial, and you can [resume the agent](#resume-subagents) to continue. The partial marking requires Claude Code v2.1.246 or later                                                                           |
| `background`      | `boolean`                                              | No       | Run this agent as a non-blocking background task when invoked                                                                                                                                                                                                                                                                                   |
| `omitClaudeMd`    | `boolean`                                              | No       | Run this agent without the user, project, and local CLAUDE.md files when it runs as a subagent; managed policy files still load. Ignored when the agent runs as the main thread agent. Requires TypeScript Agent SDK v0.3.271 or later. The Python SDK’s [`AgentDefinition`](agent-sdk-python.md#agentdefinition) doesn’t have this field |
| `effort`          | `'low' | 'medium' | 'high' | 'xhigh' | 'max' | number` | No       | Reasoning effort level for this agent                                                                                                                                                                                                                                                                                                           |
| `permissionMode`  | `PermissionMode`                                       | No       | Permission mode for tool execution within this agent. The [subagent inheritance rules](agent-sdk-permissions.md#available-modes) decide when it applies                                                                                                                                                                                   |

In the Python SDK, multi-word field names such as `disallowedTools` and `mcpServers` keep their camelCase spelling to match the wire format rather than following Python’s snake_case convention. See the [`AgentDefinition` reference](agent-sdk-python.md#agentdefinition) for details. Subagents run in the background by default. An Agent tool call that omits the [`run_in_background`](../09-Agents-Patterns/sub-agents.md#run-subagents-in-foreground-or-background) input launches a background subagent, and Claude sets `run_in_background: false` when it needs the result before continuing. Set the `background` field to `true` to force background execution for a specific agent regardless of what Claude requests. Before Claude Code v2.1.198, the background default was rolling out gradually, and an Agent tool call that omitted `run_in_background` could run the subagent synchronously. Subagents can also spawn subagents of their own. To limit how deep that nesting goes, how many subagents run at once, and how much a query spends, see [Cap subagent depth, concurrency, and spend](#cap-subagent-depth-concurrency-and-spend).


[​](#filesystem-based-definition-alternative)

Filesystem-based definition (alternative)

You can also define subagents as markdown files in `.claude/agents/` directories. See the [Claude Code subagents documentation](../09-Agents-Patterns/sub-agents.md) for details on this approach. Programmatically defined agents take precedence over filesystem-based agents with the same name.

When Claude calls the Agent tool without a `subagent_type`, it gets the built-in `general-purpose` subagent, which Claude can spawn even when you define no agents of your own. Setting [`CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1`](../02-Claude-Code-CLI/env-vars.md) removes that default, and such a call fails with [`subagent_type is required`](../02-Claude-Code-CLI/errors.md#subagent-type-is-required).


[​](#what-subagents-inherit)

What subagents inherit

Unless the subagent is a [fork](../09-Agents-Patterns/sub-agents.md#fork-the-current-conversation), its context window starts fresh, with no parent conversation, but isn’t empty. The only content you pass from parent to subagent is the Agent tool’s prompt string, so include any file paths, error messages, or decisions the subagent needs directly in that prompt. A subagent that has the [`SendMessage`](../02-Claude-Code-CLI/tools-reference.md) tool starts with a list of the other named agents running in the session, so it knows which names it can send messages to. Claude Code adds the list to the subagent’s first turn automatically. A [fork](../09-Agents-Patterns/sub-agents.md#fork-the-current-conversation) doesn’t get the list because it inherits the parent conversation instead. A subagent also inherits the main session’s extended thinking configuration. The table below lists what a non-fork subagent’s context contains and what it leaves out.

| The subagent receives                                                                                                                                                                                              | The subagent doesn’t receive                                       |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------|
| Its own system prompt (`AgentDefinition.prompt`) and the Agent tool’s prompt                                                                                                                                       | The parent’s conversation history or tool results                  |
| Project CLAUDE.md (loaded via [`settingSources`](agent-sdk-claude-code-features.md#control-filesystem-settings-with-settingsources)), unless the agent sets [`omitClaudeMd`](#agentdefinition-configuration) | Preloaded skill content, unless listed in `AgentDefinition.skills` |
| Tool definitions (inherited from parent or the subset in `tools`, [filtered for background runs](../09-Agents-Patterns/sub-agents.md#available-tools))                                                                             | The parent’s system prompt                                         |

The parent receives the subagent’s final report, but may summarize it in its own response. To preserve subagent output verbatim in the user-facing response, include an instruction to do so in the prompt or `systemPrompt` option you pass to the main `query()` call.In v2.1.210 and later, Claude Code [scans the final message for instruction-shaped patterns](../09-Agents-Patterns/sub-agents.md#subagent-output-scanning) before the parent reads it. The scan treats three kinds of pattern differently:

- **Control-tag imitation**: Claude Code neutralizes a tag that only the harness emits, such as a `<system-reminder>` block, in place. It inserts a backslash after the opening angle bracket and deletes nothing.
- **Permission-configuration mentions**: Claude Code keeps references to the permission configuration, such as `.claude/settings.json`, `bypassPermissions`, or `--dangerously-skip-permissions`, as written.
- **Turn markers**: a line that starts with `Human:` or `Assistant:` gets a backslash before the colon, so the message can’t imitate a conversation turn boundary.

For a control-tag or permission-configuration match, Claude Code prepends a `[harness: ...]` marker line naming the matched patterns; a turn-marker match doesn’t add the marker line. Those are the only modifications the scan makes: it never removes or rewords the subagent’s text.

An API error that ends the subagent early, such as a rate limit, is never delivered as its result. See [API errors in subagents](../09-Agents-Patterns/sub-agents.md#api-errors-in-subagents) for the foreground and background behavior.


[​](#invoke-subagents)

Invoke subagents


[​](#automatic-invocation)

Automatic invocation

Claude automatically decides when to invoke subagents based on the task and each subagent’s `description`. For example, if you define a `performance-optimizer` subagent with the description “Performance optimization specialist for query tuning”, Claude will invoke it when your prompt mentions optimizing queries. Write clear, specific descriptions so Claude can match tasks to the right subagent.


[​](#explicit-invocation)

Explicit invocation

To guarantee Claude uses a specific subagent, mention it by name in your prompt:

```python
"Use the code-reviewer agent to check the authentication module"
```

This bypasses automatic matching and directly invokes the named subagent.


[​](#dynamic-agent-configuration)

Dynamic agent configuration

You can create agent definitions dynamically based on runtime conditions. This example creates a security reviewer with different strictness levels, using a more capable model for strict reviews.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition


# Factory function that returns an AgentDefinition
# This pattern lets you customize agents based on runtime conditions
def create_security_agent(security_level: str) -> AgentDefinition:
    is_strict = security_level == "strict"
    return AgentDefinition(
        description="Security code reviewer",
        # Customize the prompt based on strictness level
        prompt=f"You are a {'strict' if is_strict else 'balanced'} security reviewer...",
        tools=["Read", "Grep", "Glob"],
        # Key insight: use a more capable model for high-stakes reviews
        model="opus" if is_strict else "sonnet",
    )


async def main():
    # The agent is created at query time, so each request can use different settings
    async for message in query(
        prompt="Review this PR for security issues",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                # Call the factory with your desired configuration
                "security-reviewer": create_security_agent("strict")
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)


asyncio.run(main())
```

```python
import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

// Factory function that returns an AgentDefinition
// This pattern lets you customize agents based on runtime conditions
function createSecurityAgent(securityLevel: "basic" | "strict"): AgentDefinition {
  const isStrict = securityLevel === "strict";
  return {
    description: "Security code reviewer",
    // Customize the prompt based on strictness level
    prompt: `You are a ${isStrict ? "strict" : "balanced"} security reviewer...`,
    tools: ["Read", "Grep", "Glob"],
    // Key insight: use a more capable model for high-stakes reviews
    model: isStrict ? "opus" : "sonnet"
  };
}

// The agent is created at query time, so each request can use different settings
for await (const message of query({
  prompt: "Review this PR for security issues",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      // Call the factory with your desired configuration
      "security-reviewer": createSecurityAgent("strict")
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}
```


[​](#detect-subagent-invocation)

Detect subagent invocation

Claude invokes subagents through the Agent tool. To detect when a subagent is invoked, check for `tool_use` blocks where `name` is `"Agent"`. Messages from within a subagent’s context include a `parent_tool_use_id` field. In TypeScript, each assistant and user message a subagent produces also carries [`agent_id`](agent-sdk-typescript.md#sdkassistantmessage): the `task_id` of that subagent’s [task events](agent-sdk-typescript.md#sdktaskstartedmessage). `agent_id` requires TypeScript Agent SDK v0.3.292 or later.

The tool appears as `"Agent"` in `tool_use` blocks but as `"Task"` in the `system:init` tools list. Before Claude Code v2.1.63, `tool_use` blocks also named it `"Task"`. To keep detection working across SDK versions, match both values in `block.name`.

The message structure differs between SDKs. In Python, you access content blocks directly via `message.content`. In TypeScript, `SDKAssistantMessage` wraps the Claude API message, so you access content via `message.message.content`. This example iterates through streamed messages, logging when a subagent is invoked and when subsequent messages originate from within that subagent’s execution context. The TypeScript version also logs the `agent_id` of each subagent message that carries one.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolUseBlock


async def main():
    async for message in query(
        prompt="Use the code-reviewer agent to review this codebase",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Glob", "Grep", "Agent"],
            agents={
                "code-reviewer": AgentDefinition(
                    description="Expert code reviewer.",
                    prompt="Analyze code quality and suggest improvements.",
                    tools=["Read", "Glob", "Grep"],
                )
            },
        ),
    ):
        # Check for subagent invocation. Match both names: older SDK
        # versions emitted "Task", current versions emit "Agent".
        if hasattr(message, "content") and message.content:
            for block in message.content:
                if isinstance(block, ToolUseBlock) and block.name in (
                    "Task",
                    "Agent",
                ):
                    print(f"Subagent invoked: {block.input.get('subagent_type')}")

        # Check if this message is from within a subagent's context
        if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
            print("  (running inside subagent)")

        if hasattr(message, "result"):
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Use the code-reviewer agent to review this codebase",
  options: {
    allowedTools: ["Read", "Glob", "Grep", "Agent"],
    agents: {
      "code-reviewer": {
        description: "Expert code reviewer.",
        prompt: "Analyze code quality and suggest improvements.",
        tools: ["Read", "Glob", "Grep"]
      }
    }
  }
})) {
  const msg = message as any;

  // Check for subagent invocation. Match both names: older SDK versions
  // emitted "Task", current versions emit "Agent".
  for (const block of msg.message?.content ?? []) {
    if (block.type === "tool_use" && (block.name === "Task" || block.name === "Agent")) {
      console.log(`Subagent invoked: ${block.input.subagent_type}`);
    }
  }

  // Check if this message is from within a subagent's context
  if (msg.parent_tool_use_id) {
    console.log("  (running inside subagent)");
    // On assistant and user messages, agent_id matches the task_id
    // on that subagent's task_started and other task events
    if (msg.agent_id) {
      console.log(`  agent_id: ${msg.agent_id}`);
    }
  }

  if ("result" in message) {
    console.log(message.result);
  }
}
```


[​](#resume-subagents)

Resume subagents

You can resume a subagent to continue where it left off rather than starting fresh. A resumed subagent retains its full conversation history, including all previous tool calls, results, and reasoning. When a subagent stops at its [`maxTurns`](#agentdefinition-configuration) limit, Claude Code marks the output in the Agent tool result as partial, so Claude knows the run is unfinished. When a subagent completes, the Agent tool result includes a text block containing `agentId: <id>`. The built-in [`Explore` and `Plan` agents](../09-Agents-Patterns/sub-agents.md#built-in-subagents) are one-shot and don’t return an `agentId`, so use a custom agent or `general-purpose` when you need to resume. To resume a subagent programmatically:

1.  **Capture the session ID**: extract `session_id` from messages during the first query
2.  **Extract the agent ID**: parse `agentId` from the Agent tool result text
3.  **Resume the session**: pass `resume: sessionId` in the second query’s options, and include the agent ID in your prompt. Each `query()` call starts a new session by default, and you must resume the same session to access the subagent’s transcript.

When using a custom agent, pass the same agent definition in the `agents` parameter for both queries.

The example below defines a custom `endpoint-finder` agent. The first query runs it and captures the session ID and agent ID from the Agent tool result, then the second query resumes the session to ask a follow-up question that requires context from the first analysis.

Python

TypeScript

```python
import asyncio
import re
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolResultBlock

AGENTS = {
    "endpoint-finder": AgentDefinition(
        description="Locates and catalogs API endpoints in a codebase.",
        prompt="You find and document API endpoints. Report each endpoint's path, method, and handler.",
        tools=["Read", "Grep", "Glob"],
    )
}


def extract_agent_id(block: ToolResultBlock) -> str | None:
    """Extract agentId from an Agent tool result's text content."""
    parts = block.content if isinstance(block.content, list) else [{"text": block.content}]
    for part in parts:
        if match := re.search(r"agentId:\s*([\w-]+)", part.get("text") or ""):
            return match.group(1)
    return None


async def main():
    agent_id = None
    session_id = None

    # First invocation - run the endpoint-finder subagent
    try:
        async for message in query(
            prompt="Use the endpoint-finder agent to find all API endpoints in this codebase",
            options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS),
        ):
            # Capture session_id from ResultMessage (needed to resume this session)
            if hasattr(message, "session_id"):
                session_id = message.session_id
            # Search tool results for the agentId trailer
            for block in getattr(message, "content", None) or []:
                if isinstance(block, ToolResultBlock):
                    agent_id = extract_agent_id(block) or agent_id
            # Print the final result
            if hasattr(message, "result"):
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so session_id and agent_id have already been captured by the loop above.
        print(f"Session ended with an error: {error}")

    # Second invocation - resume and ask follow-up
    if agent_id and session_id:
        async for message in query(
            prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS, resume=session_id
            ),
        ):
            if hasattr(message, "result"):
                print(message.result)
    else:
        print("No agentId found in the first query, so there is no subagent to resume.")


asyncio.run(main())
```

```python
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

const agents = {
  "endpoint-finder": {
    description: "Locates and catalogs API endpoints in a codebase.",
    prompt: "You find and document API endpoints. Report each endpoint's path, method, and handler.",
    tools: ["Read", "Grep", "Glob"]
  }
};

// Stringify content to search for agentId without traversing nested block types
function extractAgentId(message: SDKMessage): string | undefined {
  if (message.type !== "assistant" && message.type !== "user") return undefined;
  const content = JSON.stringify(message.message.content);
  const match = content.match(/agentId:\s*([\w-]+)/);
  return match?.[1];
}

let agentId: string | undefined;
let sessionId: string | undefined;

// First invocation - run the endpoint-finder subagent
try {
  for await (const message of query({
    prompt: "Use the endpoint-finder agent to find all API endpoints in this codebase",
    options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents }
  })) {
    // Capture session_id from ResultMessage (needed to resume this session)
    if ("session_id" in message) sessionId = message.session_id;
    // Search message content for the agentId (appears in Agent tool results)
    const extractedId = extractAgentId(message);
    if (extractedId) agentId = extractedId;
    // Print the final result
    if ("result" in message) console.log(message.result);
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so sessionId and agentId have already been captured by the loop above.
  console.error(`Session ended with an error: ${error}`);
}

// Second invocation - resume and ask follow-up
if (agentId && sessionId) {
  for await (const message of query({
    prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
    options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents, resume: sessionId }
  })) {
    if ("result" in message) console.log(message.result);
  }
} else {
  console.log("No agentId found in the first query, so there is no subagent to resume.");
}
```

Subagent transcripts are stored in separate files and persist independently of the main conversation. See [resume subagents in Claude Code](../09-Agents-Patterns/sub-agents.md#resume-subagents) for compaction behavior and the `cleanupPeriodDays` cleanup period.


[​](#tool-restrictions)

Tool restrictions

Use the `tools` field to limit what a subagent can do:

- **Omit `tools`**: the subagent gets every [tool available to subagents](../09-Agents-Patterns/sub-agents.md#available-tools)
- **List tools**: the subagent gets only those. A code reviewer that should never edit files, for example, gets `["Read", "Grep", "Glob"]`

A tool you leave out isn’t in the subagent’s session at all: Claude works without it, with no permission prompt or error. This example creates a read-only analysis agent that can examine code but can’t modify files or run commands.

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition


async def main():
    async for message in query(
        prompt="Analyze the architecture of this codebase",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "code-analyzer": AgentDefinition(
                    description="Static code analysis and architecture review",
                    prompt="""You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.""",
                    # Read-only tools: no Edit, Write, or Bash access
                    tools=["Read", "Grep", "Glob"],
                )
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Analyze the architecture of this codebase",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      "code-analyzer": {
        description: "Static code analysis and architecture review",
        prompt: `You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.`,
        // Read-only tools: no Edit, Write, or Bash access
        tools: ["Read", "Grep", "Glob"]
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}
```


[​](#common-tool-combinations)

Common tool combinations

| Use case           | Tools                                   | Description                                                        |
|:-------------------|:----------------------------------------|:-------------------------------------------------------------------|
| Read-only analysis | `Read`, `Grep`, `Glob`                  | Can examine code but not modify or execute                         |
| Test execution     | `Bash`, `Read`, `Grep`                  | Can run commands and analyze output                                |
| Code modification  | `Read`, `Edit`, `Write`, `Grep`, `Glob` | Full read/write access without command execution                   |
| Full access        | All tools                               | Inherits the tools available to subagents (omit the `tools` field) |


[​](#cap-subagent-depth-concurrency-and-spend)

Cap subagent depth, concurrency, and spend

This section describes TypeScript SDK v0.3.219 and Python SDK v0.2.127 and later, the releases that bundle Claude Code v2.1.219 or later. On earlier releases, some of these limits are missing or default differently, so upgrade before you rely on them to bound a run. The [environment variable reference](../02-Claude-Code-CLI/env-vars.md) and [turns and budget](agent-sdk-agent-loop.md#turns-and-budget) record the Claude Code version that added each variable and the spend cap’s subagent enforcement.

Claude decides on its own when to spawn a subagent and how many to spawn. Each subagent makes its own API requests, which count toward the query’s `total_cost_usd`, and a subagent can spawn subagents of its own, so one prompt can grow into a tree of agents. You can cap that growth in three ways: how deeply subagents nest, how many run at once, and how much the whole query spends. Set the depth and concurrency limits as environment variables through the [`env`](agent-sdk-typescript.md#options) option, and the spend limit as a query option:

| Limit       | Set it with                                                 | Default                                                                                                | What Claude Code does at the limit                                                                                                                                                                                                                                                                                                        |
|:------------|:------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Depth       | [`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`](../02-Claude-Code-CLI/env-vars.md) | `3` layers of subagents below your main agent. `1` stops your subagents from spawning any of their own | Leaves a subagent at the bottom layer unable to spawn, so it does its delegated work itself. See [nested subagents](../09-Agents-Patterns/sub-agents.md#let-subagents-spawn-their-own-subagents)                                                                                                                                                          |
| Concurrency | [`CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`](../02-Claude-Code-CLI/env-vars.md) | `20` subagents running at once, counting every subagent Claude spawns with the Agent tool              | Refuses to spawn another subagent, returning `Concurrent subagent limit reached`, until the running count drops below the limit. Sessions with [ultracode](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) active are never refused. See the [concurrent subagent limit](../09-Agents-Patterns/sub-agents.md#concurrent-subagent-limit)                        |
| Spend       | `maxBudgetUsd` in TypeScript, `max_budget_usd` in Python    | No limit. Counts the call’s own spend, subagent requests included                                      | Enforces the cap in three ways: refuses to spawn more subagents, returning `Budget limit reached`, stops background subagents that are still running, and ends the query with the `error_max_budget_usd` result subtype. For how the caps behave across a session, see [turns and budget](agent-sdk-agent-loop.md#turns-and-budget) |

The two SDKs treat the `env` option differently: the TypeScript SDK replaces the subprocess environment with it, so spread `process.env` into it to keep variables like `PATH`, while the Python SDK merges it into the inherited environment. This example turns nesting off, allows at most five subagents at a time, and stops the query once the estimated spend reaches \$5:

Python

TypeScript

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    try:
        async for message in query(
            prompt="Audit every service in this repo for unhandled promise rejections",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Grep", "Glob", "Agent"],
                # env is merged on top of the inherited environment
                env={
                    "CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH": "1",
                    "CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS": "5",
                },
                max_budget_usd=5.0,
            ),
        ):
            if isinstance(message, ResultMessage):
                print(f"{message.subtype}: ${message.total_cost_usd}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result,
        # so the budget-capped result has already been printed above.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({
    prompt: "Audit every service in this repo for unhandled promise rejections",
    options: {
      allowedTools: ["Read", "Grep", "Glob", "Agent"],
      // env replaces the subprocess environment, so spread process.env to keep PATH
      env: {
        ...process.env,
        CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: "1",
        CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: "5",
      },
      maxBudgetUsd: 5,
    },
  })) {
    if (message.type === "result") {
      console.log(`${message.subtype}: $${message.total_cost_usd}`);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the budget-capped result has already been logged above.
  console.error(`Session ended with an error: ${error}`);
}
```

What you see depends on which limit, if any, the query reaches:

- **Under the spend cap**: you see `success` and the estimated cost.
- **At the spend cap**: you see `error_max_budget_usd` with a cost at or above `5`, and then your error handler runs.
- **At the concurrency limit**: you see a `tool_result` block in the message stream carrying `Concurrent subagent limit reached`. Claude receives the same block as the Agent tool’s result.


[​](#run-opus-5-with-subagents)

Run Opus 5 with subagents

Claude Opus 5 delegates to subagents more readily than earlier models, so the [depth, concurrency, and spend limits](#cap-subagent-depth-concurrency-and-spend) matter most on queries that run Opus 5. The [Opus 5 prompting guide](../10-Prompting-Guides/build-with-claude-prompt-engineering-prompting-claude-opus-5.md#controlling-subagent-spawning) has a delegation instruction you can add to any prompt. Whether Claude Code adds an instruction of its own depends on which [system prompt](agent-sdk-modifying-system-prompts.md#how-system-prompts-work) you use:

- **`claude_code` preset**: when the model is Opus 5, Claude Code adds a line to its system prompt telling Claude not to call the Agent tool unless it’s asked to. The Agent tool stays available.
- **A custom prompt, or no `systemPrompt`**: Claude Code doesn’t build its system prompt, so that line is absent. Add the prompting guide’s delegation instruction to your own prompt.

Either instruction only steers Claude, so set the limits as well. Claude Code enforces them however Claude decides to delegate.


[​](#scale-up-with-dynamic-workflows)

Scale up with dynamic workflows

Subagents work well for a few delegated tasks per turn. For runs that coordinate dozens to hundreds of agents, use the `Workflow` tool, which moves the orchestration into a script the runtime executes outside the conversation context. See [dynamic workflows](../02-Claude-Code-CLI/workflows.md) for how workflows differ from turn-by-turn subagent delegation. The `Workflow` tool is available in the TypeScript Agent SDK v0.3.149 and later. Include `Workflow` in `allowedTools` to auto-approve workflow runs. The tool input and output schemas are listed in the [TypeScript reference](agent-sdk-typescript.md#workflow).


[​](#troubleshooting)

Troubleshooting


[​](#claude-not-delegating-to-subagents)

Claude not delegating to subagents

If Claude completes tasks directly instead of delegating to your subagent:

- **Use explicit prompting**: mention the subagent by name in your prompt, for example “Use the code-reviewer agent to check the authentication module”
- **Write a clear description**: explain exactly when to use the subagent so Claude can match tasks appropriately


[​](#filesystem-based-agents-not-loading)

Filesystem-based agents not loading

Claude Code watches `~/.claude/agents/` and `.claude/agents/` and picks up a new or edited agent file within a few seconds, with no restart needed. If a definition never appears, work through these causes:

- **New `agents` directory**: the watcher covers only directories that existed when the session started, so the first file in a new directory needs a session restart. This is the most common cause.
- **Invalid frontmatter or a duplicate `name`**: check the file’s YAML, and whether an existing agent already uses the `name`.
- **`--disable-slash-commands`**: sessions started with this flag don’t watch these directories and always need a restart to load new files.
- **A file under an added directory**: Claude Code loads `.claude/agents/` from directories added with the `add_dirs` (Python) or `additionalDirectories` (TypeScript) option, or the CLI’s `--add-dir` or `/add-dir`, but doesn’t watch them, so a new or edited file there needs a session restart.
- **A programmatic agent with the same name**: `agents` passed to `query()` override a filesystem agent with the same name.

For the file format, see [how to write subagent files](../09-Agents-Patterns/sub-agents.md#write-subagent-files).


[​](#related-documentation)

Related documentation

- [Claude Code subagents](../09-Agents-Patterns/sub-agents.md): subagent documentation including filesystem-based definitions
- [Dynamic workflows](../02-Claude-Code-CLI/workflows.md): orchestrate many subagents from a script for jobs too large for one conversation
- [SDK overview](agent-sdk-overview.md): getting started with the Claude Agent SDK

---

## Track todos - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/todo-tracking
Path: 05-Agent-SDK/agent-sdk-todo-tracking.md

## On this page

- [Model availability](#model-availability)
- [Todo lifecycle](#todo-lifecycle)
- [When Claude creates todos](#when-claude-creates-todos)
- [Examples](#examples)
  - [Monitor todo changes](#monitor-todo-changes)
  - [Display progress in real time](#display-progress-in-real-time)
- [Related documentation](#related-documentation)

Control and observability

# Track todos

Copy pageCopy page

Track todos in Agent SDK sessions and render Claude’s progress in your application from structured tool calls

Copy pageCopy page

Claude Code provides the [task-tracking tools](../02-Claude-Code-CLI/tools-reference.md#task-tool-availability) by default only on the models listed under [Model availability](#model-availability). Newer models track multi-step work without a written todo list, so on those you don’t need anything on this page for Claude to work through multi-step tasks. In a session that has the task-tracking tools, Claude keeps a written todo list, updating each item’s status as it works. You see each change in the message stream as a structured tool call. Opt a session in only when your application reads those tool calls, whether to log task activity or to render its own progress display.


[​](#model-availability)

Model availability

The following tools are available by default only on Claude 3.x models, Opus 4 through 4.7, Sonnet 4 through 4.6, and Haiku 4.5. On every other model, including model IDs Claude Code doesn’t recognize, they aren’t available unless you opt in:

- `TodoWrite`
- `TaskCreate`
- `TaskGet`
- `TaskUpdate`
- `TaskList`

Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.

On a model that doesn’t have the tools by default, unless you opt a session in, you see no `tool_use` blocks for them in the message stream. The Agent SDK applies these defaults through the Claude Code binary that it bundles. If you point `pathToClaudeCodeExecutable` (TypeScript) or `cli_path` (Python) at your own Claude Code install, you get whichever tools that install provides, under its own defaults. To see the exact set in a running session, [check which tools are available](../02-Claude-Code-CLI/tools-reference.md#check-which-tools-are-available). To opt a session in, do one of the following:

- Name one of the tools in the [`allowedTools`](agent-sdk-permissions.md#allow-and-deny-rules) (TypeScript) or `allowed_tools` (Python) option
- List the tools in the `tools` option, which restricts the session’s built-in tools to the ones it names. Include the tools you want alongside the other built-in tools you use
- Set `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` in the `env` option, as the examples on this page do. In TypeScript, `env` replaces the subprocess environment, so spread `...process.env` to keep inherited variables. In Python, `env` is merged on top of the inherited environment


[​](#todo-lifecycle)

Todo lifecycle

Claude moves each todo through a predictable lifecycle:

1.  **Created**: Claude adds the todo as `pending` when it identifies a task
2.  **Activated**: Claude sets the todo to `in_progress` when it starts the work
3.  **Completed**: Claude marks it completed when the task finishes successfully
4.  **Removed**: Claude deletes a todo it no longer needs by setting `status: "deleted"` in a `TaskUpdate` call


[​](#when-claude-creates-todos)

When Claude creates todos

In a [session that has the task-tracking tools](#model-availability), Claude creates todos for most multi-step work, such as:

- **Complex multi-step tasks** requiring three or more distinct actions
- **User-provided task lists** when multiple items are mentioned
- **Longer operations** that benefit from progress tracking
- **Explicit requests** when users ask for todo organization

Claude may skip todos for very short or single-step requests.


[​](#examples)

Examples

Before running these examples, install the Claude Agent SDK by following the [quickstart](agent-sdk-quickstart.md). Every example on this page shares the same permission setup and exit behavior:

- **Permission mode**: the example prompts ask Claude to do real work on a project, so each example sets `permissionMode: "acceptEdits"` (TypeScript) or `permission_mode="acceptEdits"` (Python) to auto-approve the file edits that work produces. See [Permission modes](agent-sdk-permissions.md#permission-modes) for the alternatives.
- **Turn limit**: each example runs until the agent finishes and yields its final result message. If a session reaches its turn limit first, that result message has the `error_max_turns` subtype. Check `subtype` to detect that ending.
- **Error handling**: these examples use single-shot `query()` calls. After yielding an `error_max_turns` result, `query()` raises an error that includes `Reached maximum number of turns`. Each example wraps its loop in a try block to exit cleanly when that happens. See [Handle the result](agent-sdk-agent-loop.md#handle-the-result) for the result subtypes.

The task system messages, [`SDKTaskNotificationMessage`](agent-sdk-typescript.md#sdktasknotificationmessage) (TypeScript) or [`TaskNotificationMessage`](agent-sdk-python.md#tasknotificationmessage) (Python) among them, report background tasks such as backgrounded commands and subagents. In the message stream, you see todo activity as `tool_use` blocks in the assistant messages.


[​](#monitor-todo-changes)

Monitor todo changes

The following example watches the assistant stream for `TaskCreate` and `TaskUpdate` `tool_use` blocks and prints a `+` line with each new task’s subject and an update line with each status change’s task ID and new status. Use this shape when you want a log of task activity rather than a rendered display. The `+` lines don’t include the assigned IDs, so this log can’t match updates back to their creates. To keep that correlation, capture the IDs as [Display progress in real time](#display-progress-in-real-time) does. The streamed `tool_use` input is the raw shape the model emitted. Claude Code repairs some close-but-incorrect key names before execution, mapping `id` or `task_id` to `taskId` and `active_form` to `activeForm`, but that repair is not reflected in the stream. Read `TaskUpdate` input fields defensively, as both examples on this page do, rather than assuming the canonical name is always present.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({
    prompt: "Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
    // Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
    options: { maxTurns: 15, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
  })) {
    if (message.type !== "assistant") continue;
    for (const block of message.message.content) {
      if (block.type !== "tool_use") continue;
      if (block.name === "TaskCreate") {
        const input = block.input as { subject: string };
        console.log(`+ ${input.subject}`);
      } else if (block.name === "TaskUpdate") {
        const input = block.input as {
          taskId?: string;
          id?: string;
          task_id?: string;
          status?: string;
        };
        const taskId = input.taskId ?? input.id ?? input.task_id;
        if (taskId && input.status) console.log(`  ${taskId} -> ${input.status}`);
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result.
  console.log(`Session ended with an error: ${error}`);
}
```

```python
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock

async def main():
    try:
        async for message in query(
            prompt="Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
            # Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
            options=ClaudeAgentOptions(max_turns=15, permission_mode="acceptEdits", env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}),
        ):
            if not isinstance(message, AssistantMessage):
                continue
            for block in message.content:
                if not isinstance(block, ToolUseBlock):
                    continue
                if block.name == "TaskCreate":
                    print(f"+ {block.input.get('subject', '')}")
                elif block.name == "TaskUpdate" and block.input.get("status"):
                    task_id = (
                        block.input.get("taskId")
                        or block.input.get("id")
                        or block.input.get("task_id")
                    )
                    if task_id:
                        print(f"  {task_id} -> {block.input['status']}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result.
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```


[​](#display-progress-in-real-time)

Display progress in real time

The following example watches the assistant stream for `TaskCreate` and `TaskUpdate` `tool_use` blocks and keeps a map of tasks keyed by task ID in a `TaskTracker` class, rerendering a progress summary on every change. The summary counts completed and in-progress tasks and shows each active item’s `activeForm` label in place of its `subject`. Use this shape when your application maintains a progress display instead of logging each event. The assigned task ID isn’t in the `TaskCreate` input. Claude Code delivers each tool’s structured output on the user message that carries its `tool_result` block, in the `tool_use_result` field. For `TaskCreate`, that object is documented for TypeScript as `TaskCreateOutput` under [Tool Output Types](agent-sdk-typescript.md#tool-output-types), and in Python the field is a plain dict of the same shape. The tracker pairs each `tool_result` block with its `tool_use` call by `tool_use_id` and reads `task.id` from the paired message’s `tool_use_result`. Claude can read the list back with `TaskList` and one task’s full details with `TaskGet`.

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

type Task = { subject: string; activeForm?: string; status: string };

class TaskTracker {
  private tasks = new Map<string, Task>();
  private pendingCreates = new Map<string, { subject: string; activeForm?: string }>();

  displayProgress() {
    if (this.tasks.size === 0) {
      console.log("\nProgress: no open tasks\n");
      return;
    }

    const items = [...this.tasks.values()];
    const completed = items.filter((t) => t.status === "completed").length;
    const inProgress = items.filter((t) => t.status === "in_progress").length;

    console.log(`\nProgress: ${completed}/${this.tasks.size} completed`);
    console.log(`Currently working on: ${inProgress} task(s)\n`);

    for (const [id, task] of this.tasks) {
      const icon =
        task.status === "completed" ? "✅" : task.status === "in_progress" ? "🔧" : "❌";
      const text = task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject;
      console.log(`${id}. ${icon} ${text}`);
    }
  }

  handleToolUse(block: { id: string; name: string; input: unknown }) {
    if (block.name === "TaskCreate") {
      const input = block.input as { subject: string; activeForm?: string; active_form?: string };
      this.pendingCreates.set(block.id, {
        subject: input.subject,
        activeForm: input.activeForm ?? input.active_form,
      });
    } else if (block.name === "TaskUpdate") {
      const input = block.input as {
        taskId?: string;
        id?: string;
        task_id?: string;
        status?: string;
        activeForm?: string;
        active_form?: string;
      };
      const taskId = input.taskId ?? input.id ?? input.task_id;
      if (!taskId) return;
      if (input.status === "deleted") {
        this.tasks.delete(taskId);
        this.displayProgress();
        return;
      }
      const task = this.tasks.get(taskId);
      if (!task) return;
      if (input.status) task.status = input.status;
      const active = input.activeForm ?? input.active_form;
      if (active) task.activeForm = active;
      this.displayProgress();
    }
  }

  handleToolResult(block: { tool_use_id: string; is_error?: boolean }, result: unknown) {
    const create = this.pendingCreates.get(block.tool_use_id);
    if (!create) return;
    this.pendingCreates.delete(block.tool_use_id);
    if (block.is_error) return;
    // The result's user message carries the tool's structured output as
    // tool_use_result; for TaskCreate that's TaskCreateOutput,
    // { task: { id, subject } }.
    const out = result as { task?: { id: string } };
    if (!out?.task?.id) return;
    this.tasks.set(out.task.id, { ...create, status: "pending" });
    this.displayProgress();
  }

  async trackQuery(prompt: string) {
    try {
      for await (const message of query({
        prompt,
        options: { maxTurns: 20, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
      })) {
        if (message.type === "assistant") {
          for (const block of message.message.content) {
            if (block.type === "tool_use") this.handleToolUse(block);
          }
        }
        if (message.type === "user" && Array.isArray(message.message.content)) {
          for (const block of message.message.content) {
            if (block.type === "tool_result") this.handleToolResult(block, message.tool_use_result);
          }
        }
      }
    } catch (error) {
      // A single-shot query() throws after yielding an error result,
      // such as when the maxTurns limit is hit.
      console.log(`Session ended with an error: ${error}`);
    }
  }
}

// Usage
const tracker = new TaskTracker();
await tracker.trackQuery("Build a complete authentication system with todos");
```

```python
import asyncio

from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    AssistantMessage,
    UserMessage,
    ToolUseBlock,
    ToolResultBlock,
)


class TaskTracker:
    def __init__(self):
        self.tasks: dict[str, dict] = {}
        self.pending_creates: dict[str, dict] = {}

    def display_progress(self):
        if not self.tasks:
            print("\nProgress: no open tasks\n")
            return

        completed = len([t for t in self.tasks.values() if t["status"] == "completed"])
        in_progress = len([t for t in self.tasks.values() if t["status"] == "in_progress"])

        print(f"\nProgress: {completed}/{len(self.tasks)} completed")
        print(f"Currently working on: {in_progress} task(s)\n")

        for task_id, task in self.tasks.items():
            icon = (
                "✅"
                if task["status"] == "completed"
                else "🔧"
                if task["status"] == "in_progress"
                else "❌"
            )
            text = (
                task["activeForm"]
                if task["status"] == "in_progress" and task.get("activeForm")
                else task["subject"]
            )
            print(f"{task_id}. {icon} {text}")

    def handle_tool_use(self, block: ToolUseBlock):
        if block.name == "TaskCreate":
            self.pending_creates[block.id] = {
                "subject": block.input.get("subject", ""),
                "activeForm": block.input.get("activeForm") or block.input.get("active_form"),
            }
        elif block.name == "TaskUpdate":
            task_id = (
                block.input.get("taskId")
                or block.input.get("id")
                or block.input.get("task_id")
            )
            if not task_id:
                return
            if block.input.get("status") == "deleted":
                self.tasks.pop(task_id, None)
                self.display_progress()
                return
            task = self.tasks.get(task_id)
            if not task:
                return
            if block.input.get("status"):
                task["status"] = block.input["status"]
            active = block.input.get("activeForm") or block.input.get("active_form")
            if active:
                task["activeForm"] = active
            self.display_progress()

    def handle_tool_result(self, block: ToolResultBlock, tool_use_result):
        create = self.pending_creates.pop(block.tool_use_id, None)
        if create is None or block.is_error:
            return
        # The result's user message carries the tool's structured output as
        # tool_use_result; for TaskCreate that's {"task": {"id": ..., "subject": ...}}.
        task = (tool_use_result or {}).get("task") or {}
        if not task.get("id"):
            return
        self.tasks[task["id"]] = {**create, "status": "pending"}
        self.display_progress()

    async def track_query(self, prompt: str):
        try:
            async for message in query(
                prompt=prompt,
                options=ClaudeAgentOptions(
                    max_turns=20,
                    permission_mode="acceptEdits",
                    env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"},
                ),
            ):
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, ToolUseBlock):
                            self.handle_tool_use(block)
                if isinstance(message, UserMessage) and isinstance(message.content, list):
                    for block in message.content:
                        if isinstance(block, ToolResultBlock):
                            self.handle_tool_result(block, message.tool_use_result)
        except Exception as error:
            # A single-shot query() raises after yielding an error result,
            # such as when the max_turns limit is hit.
            print(f"Session ended with an error: {error}")


# Usage
async def main():
    tracker = TaskTracker()
    await tracker.track_query("Build a complete authentication system with todos")


asyncio.run(main())
```


[​](#related-documentation)

Related documentation

- [Agent SDK reference - TypeScript](agent-sdk-typescript.md): the options, types, and tool schemas for the TypeScript SDK, including the Task tool input and output types
- [Agent SDK reference - Python](agent-sdk-python.md): the options, types, and tool documentation for the Python SDK
- [Streaming Input](agent-sdk-streaming-vs-single-mode.md): the two input modes, and when to use streaming input instead of the single-shot calls these examples use
- [Give Claude custom tools](agent-sdk-custom-tools.md): define your own tools with the SDK’s in-process MCP server

---

## Scale to many tools with tool search - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/tool-search
Path: 05-Agent-SDK/agent-sdk-tool-search.md

## On this page

- [How tool search works](#how-tool-search-works)
- [Configure tool search](#configure-tool-search)
- [Optimize tool discovery](#optimize-tool-discovery)
- [Limits](#limits)
- [Related documentation](#related-documentation)

Extend with tools

# Scale to many tools with tool search

Copy pageCopy page

Scale your agent to thousands of tools by discovering and loading only what’s needed, on demand.

Copy pageCopy page

Tool search enables your agent to work with hundreds or thousands of tools by dynamically discovering and loading them on demand. Instead of loading all tool definitions into the context window upfront, the agent searches your tool catalog and loads only the tools it needs. This approach solves two challenges as tool libraries scale:

- **Context efficiency:** Tool definitions can consume large portions of the context window (50 tools can use 10-20K tokens), leaving less room for actual work.
- **Tool selection accuracy:** Tool selection accuracy degrades with more than 30-50 tools loaded at once.


[​](#how-tool-search-works)

How tool search works

Tool search is on by default, with the exceptions listed in [Configure tool search](#configure-tool-search). When it is active, tool definitions are withheld from the context window. The agent receives a summary of available tools and searches for relevant ones when the task requires a capability not already loaded. Up to five of the most relevant tools are loaded into context by default, where they stay available for subsequent turns until the SDK compacts the messages where the agent discovered them. After that compaction, the agent searches for those tools again when it next needs them. Tool search adds one extra round-trip each time Claude searches for tools, but for large tool sets this is offset by smaller context on every turn. With fewer than ~10 tools whose definitions fit comfortably in the context window, loading everything upfront is typically faster. For details on the underlying API mechanism, see [Tool search in the API](../04-API-Reference/Agents-Tools/agents-and-tools-tool-use-tool-search-tool.md).

Tool search isn’t supported on Microsoft Foundry [deployments hosted on Azure](../04-API-Reference/Guides/build-with-claude-claude-in-microsoft-foundry.md#hosting-options), which reject it server-side: the SDK detects the rejection and loads tool definitions upfront for that deployment instead. [`ENABLE_TOOL_SEARCH`](#configure-tool-search) can’t override this, since the rejection comes from the deployment itself.


[​](#configure-tool-search)

Configure tool search

Tool search is on by default. For models on the SDK’s unsupported-model list, the SDK loads tool definitions upfront instead, and no `ENABLE_TOOL_SEARCH` value overrides that. On Google Cloud’s Agent Platform, the SDK decides by model generation:

- **Claude Opus 4.5, Sonnet 4.5, Haiku 4.5, and later**: tool search is on by default.
- **Earlier Agent Platform models**: the SDK loads tool definitions upfront, because their serving stacks reject the required beta header. `ENABLE_TOOL_SEARCH` can’t override this.

Before Claude Code v2.1.221, the SDK disabled tool search for all models on Google Cloud’s Agent Platform unless you set `ENABLE_TOOL_SEARCH`. The SDK also disables tool search when `ANTHROPIC_BASE_URL` points to a non-first-party host, since most proxies don’t forward `tool_reference` blocks. You can override that default with the `ENABLE_TOOL_SEARCH` environment variable:

| Value    | Behavior                                                                                                                                                                                                                                                                                                                                                                                                            |
|:---------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| (unset)  | Tool search is on. Tool definitions are deferred and discovered on demand. Falls back to loading upfront on Google Cloud’s Agent Platform models earlier than the Claude 4.5 generation, a non-first-party `ANTHROPIC_BASE_URL`, or a Microsoft Foundry deployment hosted on Azure.                                                                                                                                 |
| `true`   | Tool search is always on, except on a Microsoft Foundry deployment hosted on Azure, where the server-side rejection still forces upfront loading, and on Google Cloud’s Agent Platform models earlier than the Claude 4.5 generation, where the SDK keeps loading tool definitions upfront. The SDK sends the beta header through proxies, and requests fail on proxies that don’t support `tool_reference` blocks. |
| `auto`   | Counts the tokens in the tool definitions that tool search can defer and compares the total against the model’s context window. When the total reaches 10% of the window, tool search activates. Below that, the SDK loads every tool definition into context upfront.                                                                                                                                              |
| `auto:N` | Same as `auto` with a custom percentage. `auto:5` activates when those definitions reach 5% of the context window. Lower values activate sooner.                                                                                                                                                                                                                                                                    |
| `false`  | Tool search is off. All tool definitions are loaded into context on every turn.                                                                                                                                                                                                                                                                                                                                     |

Setting [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`](../02-Claude-Code-CLI/env-vars.md) keeps tool search off. You can’t override it by setting `ENABLE_TOOL_SEARCH` yourself. Your organization can keep tool search on through [managed settings](../13-Enterprise-Admin/managed-settings.md), on Claude Code v2.1.227 or later. [Disable pre-release capabilities](../13-Enterprise-Admin/llm-gateway-protocol.md#disable-pre-release-capabilities) covers where the override applies and what the variable strips. Tool search applies to all registered tools, whether they come from remote MCP servers or [custom SDK MCP servers](agent-sdk-custom-tools.md). When you use `auto`, the SDK counts every definition that tool search can defer toward one combined threshold: each MCP tool that isn’t marked [`alwaysLoad`](../06-MCP-Tools/General/mcp.md#exempt-a-server-from-deferral), from any server, plus the built-in tools that load on demand. The SDK loads core built-in tools such as Bash, Read, and Edit upfront and doesn’t count them toward the threshold. A [mod](../08-Plugins-Skills/plugins-mods-reference.md#tools) in one of your plugins can also defer a tool or load it upfront, which changes that count. Set the value in the `env` option on `query()`. In TypeScript, `env` replaces the subprocess environment, so spread `...process.env` to keep inherited variables. In Python, `env` is merged on top of the inherited environment. This example connects to a remote MCP server that exposes many tools, pre-approves all of them with a wildcard, and uses `auto:5` so tool search activates when the definitions it can defer reach 5% of the context window:

TypeScript

Python

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({
    prompt: "Find and run the appropriate database query",
    options: {
      mcpServers: {
        "enterprise-tools": {
          // Connect to a remote MCP server
          type: "http",
          url: "https://tools.example.com/mcp"
        }
      },
      allowedTools: ["mcp__enterprise-tools__*"], // Wildcard pre-approves all tools from this server
      env: {
        ...process.env, // env replaces the subprocess environment, so keep inherited variables
        ENABLE_TOOL_SEARCH: "auto:5" // Activate tool search when deferrable definitions reach 5% of context
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result
  console.log(`Session ended with an error: ${error}`);
}
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={
            "enterprise-tools": {
                "type": "http",
                "url": "https://tools.example.com/mcp",
            }
        },
        allowed_tools=[
            "mcp__enterprise-tools__*"
        ],  # Wildcard pre-approves all tools from this server
        env={
            "ENABLE_TOOL_SEARCH": "auto:5"  # Activate tool search when deferrable definitions reach 5% of context
        },
    )

    try:
        async for message in query(
            prompt="Find and run the appropriate database query",
            options=options,
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        # A single-shot query() raises after yielding an error result
        print(f"Session ended with an error: {error}")


asyncio.run(main())
```

To run this example, replace `https://tools.example.com/mcp` with the URL of your own MCP server. On success the result text prints to the console. Because this is a single-shot `query()` call, the SDK raises after yielding an error result, so the example wraps the loop in a try block. To see why a run failed, check the result message’s `subtype`, such as `error_during_execution`, inside the loop. For more on result messages, see [Handle the result](agent-sdk-agent-loop.md#handle-the-result).


[​](#optimize-tool-discovery)

Optimize tool discovery

The search mechanism matches queries against tool names and descriptions. Names like `search_slack_messages` surface for a wider range of requests than `query_slack`. Descriptions with specific keywords (“Search Slack messages by keyword, channel, or date range”) match more queries than generic ones (“Query Slack”). You can also add a system prompt section listing available tool categories. This gives the agent context about what kinds of tools are available to search for. Pass the text in the `systemPrompt` option of `query()` in TypeScript, or `system_prompt` in Python. Use the `claude_code` preset with `append`, which adds your text to the preset’s prompt instead of replacing it:

TypeScript

Python

```python
options: {
  systemPrompt: {
    type: "preset",
    preset: "claude_code",
    append: "You can search for tools to interact with Slack, GitHub, and Jira."
  }
}
```

```python
options = ClaudeAgentOptions(
    system_prompt={
        "type": "preset",
        "preset": "claude_code",
        "append": "You can search for tools to interact with Slack, GitHub, and Jira.",
    }
)
```

For the full set of system prompt options, see [Modifying system prompts](agent-sdk-modifying-system-prompts.md).


[​](#limits)

Limits

- **Maximum tools:** 10,000 tools in your catalog
- **Search results:** returns up to five most relevant tools per search by default
- **Model support:** Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.5, and later models; see [model compatibility in the API docs](../04-API-Reference/Agents-Tools/agents-and-tools-tool-use-tool-search-tool.md#model-compatibility) for the current list. The same minimums apply on Google Cloud’s Agent Platform.


[​](#related-documentation)

Related documentation

- [Tool search in the API](../04-API-Reference/Agents-Tools/agents-and-tools-tool-use-tool-search-tool.md): Full API documentation for tool search, including custom implementations
- [Connect MCP servers](agent-sdk-mcp.md): Connect to external tools via MCP servers
- [Custom tools](agent-sdk-custom-tools.md): Build your own tools with SDK MCP servers
- [TypeScript SDK reference](agent-sdk-typescript.md): Full API reference
- [Python SDK reference](agent-sdk-python.md): Full API reference

---

## Troubleshoot the Agent SDK - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/troubleshooting
Path: 05-Agent-SDK/agent-sdk-troubleshooting.md

## On this page

- [CLI startup](#cli-startup)
  - [CLINotFoundError: Claude Code not found](#clinotfounderror-claude-code-not-found)
  - [CLIConnectionError: Refusing to execute batch script](#cliconnectionerror-refusing-to-execute-batch-script)
  - [CLIConnectionError: Failed to start Claude Code](#cliconnectionerror-failed-to-start-claude-code)
  - [CLIConnectionError: Not connected](#cliconnectionerror-not-connected)
- [CLI process exit](#cli-process-exit)
  - [ProcessError: Command failed with exit code](#processerror-command-failed-with-exit-code)
  - [Claude Code process exited with code N](#claude-code-process-exited-with-code-n)
  - [Claude Code returned an error result](#claude-code-returned-an-error-result)
- [Structured outputs](#structured-outputs)
  - [structured_output is None but the result says success](#structured_output-is-none-but-the-result-says-success)
- [Report a new issue](#report-a-new-issue)

Agent SDK

# Troubleshoot the Agent SDK

Copy pageCopy page

Fix Agent SDK errors when the Claude Code CLI fails to start, the CLI process exits, or a successful result arrives without structured output.

Copy pageCopy page

This page covers Agent SDK errors in CLI startup, CLI process exit, and structured outputs. Entries on this page are keyed to the error you see. Each entry names the cause and what to do. Symptoms tied to a feature, such as a hook not firing or a skill not being used, have a troubleshooting section on that feature’s page. The table names the section or page that covers each symptom:

| Symptom                                                                                                                                                                                                                                                                               | Go to                                                                                                                          |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|
| Skills not found, a skill not being used, `Invalid skill name` error                                                                                                                                                                                                                  | [Skills troubleshooting](agent-sdk-skills.md#troubleshooting)                                                            |
| MCP server shows `failed` status, tools not being called, a tool missing from an SDK MCP server, connection timeouts, tool output that exceeds the maximum allowed tokens                                                                                                             | [MCP troubleshooting](agent-sdk-mcp.md#troubleshooting)                                                                  |
| Plugin not loading, plugin skills not appearing                                                                                                                                                                                                                                       | [Plugins troubleshooting](agent-sdk-plugins.md#troubleshooting)                                                          |
| Claude not delegating to subagents, filesystem-based agents not loading                                                                                                                                                                                                               | [Subagents troubleshooting](agent-sdk-subagents.md#troubleshooting)                                                      |
| Checkpointing options not recognized, user messages without UUIDs, `No file checkpoint found`, `File rewinding is not enabled`, `ProcessTransport is not ready for writing`                                                                                                           | [File checkpointing troubleshooting](agent-sdk-file-checkpointing.md#troubleshooting)                                    |
| Hook not firing, matcher not filtering as expected, hook timeout, tool blocked unexpectedly, modified input not applied, session hooks not available in Python, subagent permission prompts multiplying, recursive hook loops with subagents, `systemMessage` not appearing in output | [Fix common issues](agent-sdk-hooks.md#fix-common-issues) on the hooks page                                              |
| An agent that works on your machine fails in a deployed service or container                                                                                                                                                                                                          | [Troubleshoot deployment failures](agent-sdk-hosting.md#troubleshoot-deployment-failures)                                |
| `Not logged in`, `Invalid API key`, `API Error`, `429`, `There's an issue with the selected model`                                                                                                                                                                                    | [Error reference](../02-Claude-Code-CLI/errors.md#find-your-error)                                                                             |
| `CLINotFoundError`, `CLIConnectionError`, `ProcessError`, `Claude Code process exited with code N`, `Claude Code returned an error result`, `structured_output` is `None`                                                                                                             | [CLI startup](#cli-startup), [CLI process exit](#cli-process-exit), and [Structured outputs](#structured-outputs) on this page |


[​](#cli-startup)

CLI startup


[​](#clinotfounderror-claude-code-not-found)

CLINotFoundError: Claude Code not found

The Python SDK launches the Claude Code CLI as a subprocess. When it can’t find a `claude` executable, connecting fails with a `CLINotFoundError`:

```python
Claude Code not found at: /your/configured/path
```

The message includes the configured path when you set `ClaudeAgentOptions(cli_path=...)` and it points at a missing file. Without `cli_path`, the SDK searches your `PATH` and common install locations, and the message includes install instructions for your platform. To fix it:

- Install Claude Code if it isn’t installed. See [Install Claude Code](../01-Getting-Started/setup.md#install-claude-code) for the command on your platform.
- If you set `cli_path`, confirm the file exists and is the `claude` executable.
- If you rely on `PATH` resolution, confirm `claude --version` works in the same environment your application runs in. Processes you launch outside your shell, such as from an IDE or a service manager, often run with a different `PATH`.

The TypeScript SDK looks for the CLI in its bundled platform package and the path you set in `pathToClaudeCodeExecutable`. Match the message you see:

- `Native CLI binary for <platform>-<arch> not found`: the bundled platform package is missing, most often because the install skipped optional dependencies. Reinstall `@anthropic-ai/claude-agent-sdk` without skipping optional dependencies, or point `pathToClaudeCodeExecutable` at a [native install](../01-Getting-Started/setup.md#install-claude-code). In a single-file executable built with `bun build --compile`, the same message has a different cause and fix. See [Compile to a single executable](agent-sdk-typescript.md#compile-to-a-single-executable).
- `Claude Code native binary not found at <path>` or `Claude Code executable not found at <path>. Is options.pathToClaudeCodeExecutable set?`: the file at the resolved path is missing, or the process can’t access it. Confirm the file exists at that path and that the process can access it.


[​](#cliconnectionerror-refusing-to-execute-batch-script)

CLIConnectionError: Refusing to execute batch script

On Windows, connecting fails with a `CLIConnectionError` when the CLI path the Python SDK uses is a `.bat` or `.cmd` batch script, including the `claude.cmd` shim that an npm install creates:

```python
Refusing to execute batch script 'C:\\Users\\you\\AppData\\Roaming\\npm\\claude.cmd': Windows runs .bat/.cmd files via cmd.exe, which can execute commands injected through CLI arguments, and no reliable escaping for cmd.exe exists. Use a native claude executable instead: install Claude Code natively (irm https://claude.ai/install.ps1 | iex), point ClaudeAgentOptions(cli_path=...) at a claude.exe, or install the claude-agent-sdk wheel for a platform that bundles claude.exe (e.g. Windows x64).
```

The refusal is deliberate security hardening, not a broken install. Windows runs batch scripts by rewriting the spawn into a `cmd.exe /c` invocation, and `cmd.exe` re-parses the whole command line at execution time, so an argument value can execute injected commands. Most Windows installs never reach this error. The Windows x64 wheel of `claude-agent-sdk` bundles a `claude.exe`, and the SDK prefers the bundled CLI, then any native `claude.exe` it can discover, before falling back to a batch shim. You see the refusal in two cases:

- You set `ClaudeAgentOptions(cli_path=...)` to a `.bat` or `.cmd` file, such as npm’s `claude.cmd` shim.
- Your install has no bundled or native `claude.exe`, for example a source install on ARM64 Windows where the only `claude` on your `PATH` is the npm shim.

To fix it, give the SDK a native executable instead of a batch script:

- If you set `ClaudeAgentOptions(cli_path=...)`, point it at a `claude.exe` or remove the option. The SDK skips discovery while `cli_path` is set, so a native install alone can’t take effect.
- Install Claude Code natively in PowerShell: `irm https://claude.ai/install.ps1 | iex`
- On x64 Windows, install the `claude-agent-sdk` wheel, which bundles `claude.exe`.

Before `claude-agent-sdk` 0.2.124, the Python SDK spawned batch scripts through `cmd.exe` without this check.


[​](#cliconnectionerror-failed-to-start-claude-code)

CLIConnectionError: Failed to start Claude Code

The SDK found a file at the resolved path but couldn’t launch it. Python raises these failures as a `CLIConnectionError`. TypeScript rejects the message iteration with an error carrying no SDK class. The table below maps each message to what it tells you. Match the message you see:

| Message                                                           | SDK        | What it tells you                                                    |
|-------------------------------------------------------------------|------------|----------------------------------------------------------------------|
| `Failed to start Claude Code: <detail>`                           | Python     | The rest of the message is the operating system’s own error          |
| `Claude Code executable at <path> exists but failed to launch`    | TypeScript | The script at the configured path can’t run                          |
| `Claude Code native binary at <path> exists but failed to launch` | TypeScript | The binary can’t run, with a libc suggestion appended to the message |
| `Failed to spawn Claude Code process: <detail>`                   | TypeScript | Any other launch failure                                             |

In both SDKs, the usual cause is a resolved path that points at something that can’t run, such as a text file, a directory, or a file without execute permission. Read the native-binary message’s libc suggestion as one possible cause. To fix it in either SDK:

- Confirm the configured path points at the `claude` executable itself and that the file has execute permission.
- If you don’t need a custom path, remove `cli_path` in Python or `pathToClaudeCodeExecutable` in TypeScript so the SDK finds a CLI on its own, preferring its bundled copy.
- When the failing binary is the SDK’s bundled copy in a container image, reinstall the SDK during the image build so the bundled binary matches the container’s platform, or rebuild the image for the architecture it runs on. The usual cause is a binary that doesn’t match the container’s architecture or libc, or one that lost its execute permission in the image build.


[​](#cliconnectionerror-not-connected)

CLIConnectionError: Not connected

Calling a `ClaudeSDKClient` method in Python before the client has connected, or after it has disconnected, raises a `CLIConnectionError` with this message:

```python
Not connected. Call connect() first.
```

Do what the message says. Either call `await client.connect()` before any other client method, or open the client with `async with ClaudeSDKClient() as client:`, which connects on entry.


[​](#cli-process-exit)

CLI process exit

The entries in this section mean the Claude Code process ended while your application was using it. Which error you see depends on the SDK language and on whether the CLI reported an error result before it exited.


[​](#processerror-command-failed-with-exit-code)

ProcessError: Command failed with exit code

The Python SDK raises a `ProcessError` when the Claude Code process exits with a nonzero code:

```python
Command failed with exit code 1 (exit code: 1)
Error output: Check stderr output for details
```

The message states the exit code twice, and the `Error output` line is fixed text rather than your process’s error output. The same fixed text fills the exception’s `stderr` attribute. The exception’s `exit_code` attribute carries the code. To capture what the CLI actually wrote to stderr, pass a `stderr` callback in `ClaudeAgentOptions` and log what it receives. A bare `ProcessError` means the CLI exited without reporting an error result. When the CLI did report one, the SDK raises [`ResultError`](agent-sdk-python.md#resulterror) instead, covered in [Claude Code returned an error result](#claude-code-returned-an-error-result). `ResultError` subclasses `ProcessError`, so `except ProcessError` catches both. To handle them differently, put the `except ResultError` clause first. Before `claude-agent-sdk` 0.2.140, the Python SDK raised error-result exits as a plain `Exception` rather than a `ResultError`.


[​](#claude-code-process-exited-with-code-n)

Claude Code process exited with code N

IDE wrappers print this message too, and the [error reference](../02-Claude-Code-CLI/errors.md#claude-code-process-exited-with-code-n) covers it for VS Code and other launchers. This entry covers what your TypeScript SDK code receives. The SDK surfaces a nonzero CLI exit as a plain `Error` that rejects the `for await` loop over `query()`’s messages. There’s no SDK error class to catch, so wrap the loop in `try`/`catch` and match on the message:

```python
Claude Code process exited with code 1. stderr: <tail of the CLI's stderr>
```

When the CLI wrote to stderr, the message ends with the tail of it. To capture the full stream, pass a `stderr` callback in the query options. A process killed by a signal reports `Claude Code process terminated by signal <name>` in the same form.


[​](#claude-code-returned-an-error-result)

Claude Code returned an error result

Both SDKs replace the process-exit error with this message when the CLI reported an error result before exiting:

```python
Claude Code returned an error result: <the CLI's own error report>
```

The text after the colon is the CLI’s report of what went wrong, so start there rather than with the exit itself. Python raises this as a [`ResultError`](agent-sdk-python.md#resulterror), whose `data` attribute carries the full error result. TypeScript rejects the message loop with a plain `Error` carrying the same message shape.


[​](#structured-outputs)

Structured outputs


[​](#structured_output-is-none-but-the-result-says-success)

structured_output is None but the result says success

A result message can end with `subtype: "success"` while `structured_output` is `None` in Python or `undefined` in TypeScript. The run completes, but no validated output exists. One way to hit this is a schema no output can satisfy, for example conflicting length constraints. Treat this result as a failure in application code. Check both that `subtype` is `success` and that `structured_output` is present before using it. The [Error handling](agent-sdk-structured-outputs.md#error-handling) section shows this pattern for both SDKs. If it happens repeatedly with a schema you believe is correct, verify the schema is satisfiable, then simplify it until outputs validate, and reintroduce constraints one at a time.


[​](#report-a-new-issue)

Report a new issue

If your error isn’t covered here, check the open issues or file a new one in the SDK repositories: [claude-agent-sdk-typescript](https://github.com/anthropics/claude-agent-sdk-typescript/issues) or [claude-agent-sdk-python](https://github.com/anthropics/claude-agent-sdk-python/issues). Include the full error text and your SDK version.

---

## TypeScript SDK V2 session API (removed) - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/typescript-v2-preview
Path: 05-Agent-SDK/agent-sdk-typescript-v2-preview.md

## On this page

- [Installation](#installation)
- [Quick start](#quick-start)
  - [One-shot prompt](#one-shot-prompt)
  - [Basic session](#basic-session)
  - [Multi-turn conversation](#multi-turn-conversation)
  - [Session resume](#session-resume)
  - [Cleanup](#cleanup)
- [API reference](#api-reference)
  - [unstable_v2_createSession()](#unstable_v2_createsession)
  - [unstable_v2_resumeSession()](#unstable_v2_resumesession)
  - [unstable_v2_prompt()](#unstable_v2_prompt)
  - [SDKSession interface](#sdksession-interface)
- [Feature availability](#feature-availability)
- [See also](#see-also)

SDK references

# TypeScript SDK V2 session API (removed)

Copy pageCopy page

Reference for the removed V2 TypeScript Agent SDK session API, with session-based send/stream patterns for multi-turn conversations.

Copy pageCopy page

The V2 session API is no longer supported. TypeScript Agent SDK 0.3.142 removes `unstable_v2_createSession`, `unstable_v2_resumeSession`, `unstable_v2_prompt`, and the `SDKSession` and `SDKSessionOptions` types.To migrate, use the [`query()` API](agent-sdk-typescript.md) and the [session options](agent-sdk-sessions.md) it accepts. Pass an `AsyncIterable<SDKUserMessage>` for multi-turn conversations, or `options.resume` to continue a saved session. This page is kept for reference if you maintain code on Agent SDK 0.2.x or earlier.

V2 was an experimental session API that removed the need for async generators and yield coordination. Instead of managing generator state across turns, each turn was a separate `send()`/`stream()` cycle. The API surface reduced to creating a session, sending a message, and streaming the response:

- `createSession()` / `resumeSession()`: Start or continue a conversation
- `session.send()`: Send a message
- `session.stream()`: Get the response


[​](#installation)

Installation

Agent SDK 0.2.x is the last version that includes the V2 interface. The package version jumped from 0.2.x directly to 0.3.142, so the removal version above and the install pin below describe the same boundary. To install the last V2-compatible release, pin the major and minor version:

```python
npm install @anthropic-ai/claude-agent-sdk@0.2
```

The SDK bundles a native Claude Code binary for your platform as an optional dependency, so most installs need no separate Claude Code install. See the [quickstart’s install note](agent-sdk-quickstart.md) for the installs that need one.


[​](#quick-start)

Quick start


[​](#one-shot-prompt)

One-shot prompt

For simple single-turn queries where you don’t need to maintain a session, use `unstable_v2_prompt()`. This example sends a math question and logs the answer:

```python
import { unstable_v2_prompt } from "@anthropic-ai/claude-agent-sdk";

const result = await unstable_v2_prompt("What is 2 + 2?", {
  model: "claude-opus-4-7"
});
if (result.subtype === "success") {
  console.log(result.result);
}
```


[​](#basic-session)

Basic session

For interactions beyond a single prompt, create a session. V2 separates sending and streaming into distinct steps:

- `send()` dispatches your message
- `stream()` streams back the response

This explicit separation makes it easier to add logic between turns (like processing responses before sending follow-ups). The example below creates a session, sends “Hello!” to Claude, and prints the text response. It uses [`await using`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) (TypeScript 5.2+) to automatically close the session when the block exits. You can also call `session.close()` manually.

```python
import { unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";

await using session = unstable_v2_createSession({
  model: "claude-opus-4-7"
});

await session.send("Hello!");
for await (const msg of session.stream()) {
  // Filter for assistant messages to get human-readable output
  if (msg.type === "assistant") {
    const text = msg.message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");
    console.log(text);
  }
}
```


[​](#multi-turn-conversation)

Multi-turn conversation

Sessions persist context across multiple exchanges. To continue a conversation, call `send()` again on the same session. Claude remembers the previous turns. This example asks a math question, then asks a follow-up that references the previous answer:

```python
import { unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";

await using session = unstable_v2_createSession({
  model: "claude-opus-4-7"
});

// Turn 1
await session.send("What is 5 + 3?");
for await (const msg of session.stream()) {
  // Filter for assistant messages to get human-readable output
  if (msg.type === "assistant") {
    const text = msg.message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");
    console.log(text);
  }
}

// Turn 2
await session.send("Multiply that by 2");
for await (const msg of session.stream()) {
  if (msg.type === "assistant") {
    const text = msg.message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");
    console.log(text);
  }
}
```


[​](#session-resume)

Session resume

If you have a session ID from a previous interaction, you can resume it later. This is useful for long-running workflows or when you need to persist conversations across application restarts. This example creates a session, stores its ID, closes it, then resumes the conversation:

```python
import {
  unstable_v2_createSession,
  unstable_v2_resumeSession,
  type SDKMessage
} from "@anthropic-ai/claude-agent-sdk";

// Helper to extract text from assistant messages
function getAssistantText(msg: SDKMessage): string | null {
  if (msg.type !== "assistant") return null;
  return msg.message.content
    .filter((block) => block.type === "text")
    .map((block) => block.text)
    .join("");
}

// Create initial session and have a conversation
const session = unstable_v2_createSession({
  model: "claude-opus-4-7"
});

await session.send("Remember this number: 42");

// Get the session ID from any received message
let sessionId: string | undefined;
for await (const msg of session.stream()) {
  sessionId = msg.session_id;
  const text = getAssistantText(msg);
  if (text) console.log("Initial response:", text);
}

console.log("Session ID:", sessionId);
session.close();

// Later: resume the session using the stored ID
await using resumedSession = unstable_v2_resumeSession(sessionId!, {
  model: "claude-opus-4-7"
});

await resumedSession.send("What number did I ask you to remember?");
for await (const msg of resumedSession.stream()) {
  const text = getAssistantText(msg);
  if (text) console.log("Resumed response:", text);
}
```


[​](#cleanup)

Cleanup

Sessions can be closed manually or automatically using [`await using`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management), a TypeScript 5.2+ feature for automatic resource cleanup. If you’re using an older TypeScript version or encounter compatibility issues, use manual cleanup instead. The examples below show only the cleanup pattern and don’t send any messages, so running them produces no output. **Automatic cleanup (TypeScript 5.2+):**

```python
import { unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";

await using session = unstable_v2_createSession({
  model: "claude-opus-4-7"
});
// Session closes automatically when the block exits
```

**Manual cleanup:**

```python
import { unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";

const session = unstable_v2_createSession({
  model: "claude-opus-4-7"
});
// ... use the session ...
session.close();
```


[​](#api-reference)

API reference


[​](#unstable_v2_createsession)

`unstable_v2_createSession()`

Creates a new session for multi-turn conversations.

```python
function unstable_v2_createSession(options: {
  model: string;
  // Additional options supported
}): SDKSession;
```


[​](#unstable_v2_resumesession)

`unstable_v2_resumeSession()`

Resumes an existing session by ID.

```python
function unstable_v2_resumeSession(
  sessionId: string,
  options: {
    model: string;
    // Additional options supported
  }
): SDKSession;
```


[​](#unstable_v2_prompt)

`unstable_v2_prompt()`

One-shot convenience function for single-turn queries.

```python
function unstable_v2_prompt(
  prompt: string,
  options: {
    model: string;
    // Additional options supported
  }
): Promise<SDKResultMessage>;
```


[​](#sdksession-interface)

SDKSession interface

```python
interface SDKSession {
  readonly sessionId: string;
  send(message: string | SDKUserMessage): Promise<void>;
  stream(): AsyncGenerator<SDKMessage, void>;
  close(): void;
}
```


[​](#feature-availability)

Feature availability

The V2 session API does not support every V1 feature. The following require the [V1 SDK](agent-sdk-typescript.md):

- Session forking (`forkSession` option)
- Some advanced streaming input patterns


[​](#see-also)

See also

- [TypeScript SDK reference (V1)](agent-sdk-typescript.md) - Full V1 SDK documentation
- [SDK overview](agent-sdk-overview.md) - General SDK concepts
- [V2 examples on GitHub](https://github.com/anthropics/claude-agent-sdk-demos/tree/main/hello-world-v2) - Working code examples

---

## Agent SDK reference - TypeScript - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/typescript
Path: 05-Agent-SDK/agent-sdk-typescript.md

## On this page

- [Installation](#installation)
  - [Compile to a single executable](#compile-to-a-single-executable)
  - [Import the /core entry when you bundle the Agent SDK](#import-the-%2Fcore-entry-when-you-bundle-the-agent-sdk)
- [Functions](#functions)
  - [query()](#query)
  - [Parameters](#parameters)
  - [Returns](#returns)
  - [startup()](#startup)
  - [Parameters](#parameters-2)
  - [Returns](#returns-2)
  - [Example](#example)
  - [prewarm()](#prewarm)
  - [Example](#example-2)
  - [tool()](#tool)
  - [Parameters](#parameters-3)
  - [ToolAnnotations](#toolannotations)
  - [createSdkMcpServer()](#createsdkmcpserver)
  - [Parameters](#parameters-4)
  - [listSessions()](#listsessions)
  - [Parameters](#parameters-5)
  - [Return type: SDKSessionInfo](#return-type-sdksessioninfo)
  - [Example](#example-3)
  - [getSessionMessages()](#getsessionmessages)
  - [Parameters](#parameters-6)
  - [Return type: SessionMessage](#return-type-sessionmessage)
  - [Example](#example-4)
  - [getSessionInfo()](#getsessioninfo)
  - [Parameters](#parameters-7)
  - [renameSession()](#renamesession)
  - [Parameters](#parameters-8)
  - [tagSession()](#tagsession)
  - [Parameters](#parameters-9)
  - [resolveSettings()](#resolvesettings)
  - [Parameters](#parameters-10)
  - [Return type: ResolvedSettings](#return-type-resolvedsettings)
  - [Example](#example-5)
- [Types](#types)
  - [Options](#options)
  - [Handle slow or stalled API responses](#handle-slow-or-stalled-api-responses)
  - [Query object](#query-object)
  - [Methods](#methods)
  - [applyFlagSettings()](#applyflagsettings)
  - [updateSettings()](#updatesettings)
  - [toggleMcpServer()](#togglemcpserver)
  - [WarmQuery](#warmquery)
  - [Methods](#methods-2)
  - [SpareProcess](#spareprocess)
  - [Members](#members)
  - [SDKControlInitializeResponse](#sdkcontrolinitializeresponse)
  - [SDKControlInterruptResponse](#sdkcontrolinterruptresponse)
  - [SDKControlGetContextUsageResponse](#sdkcontrolgetcontextusageresponse)
  - [SDKControlReadFileResponse](#sdkcontrolreadfileresponse)
  - [What readFile() can read](#what-readfile-can-read)
  - [SDKControlReloadPluginsResponse](#sdkcontrolreloadpluginsresponse)
  - [SDKControlReloadSkillsResponse](#sdkcontrolreloadskillsresponse)
  - [SDKControlReloadOutputStylesResponse](#sdkcontrolreloadoutputstylesresponse)
  - [SDKControlMcpReadResourceResponse](#sdkcontrolmcpreadresourceresponse)
  - [AgentDefinition](#agentdefinition)
  - [AgentMcpServerSpec](#agentmcpserverspec)
  - [SettingSource](#settingsource)
  - [Default behavior](#default-behavior)
  - [Why use settingSources](#why-use-settingsources)
  - [Settings precedence](#settings-precedence)
  - [PermissionMode](#permissionmode)
  - [CanUseTool](#canusetool)
  - [PermissionResult](#permissionresult)
  - [ToolConfig](#toolconfig)
  - [McpServerConfig](#mcpserverconfig)
  - [McpStdioServerConfig](#mcpstdioserverconfig)
  - [McpSSEServerConfig](#mcpsseserverconfig)
  - [McpHttpServerConfig](#mcphttpserverconfig)
  - [McpSdkServerConfigWithInstance](#mcpsdkserverconfigwithinstance)
  - [McpClaudeAIProxyServerConfig](#mcpclaudeaiproxyserverconfig)
  - [SdkPluginConfig](#sdkpluginconfig)
- [Message Types](#message-types)
  - [SDKMessage](#sdkmessage)
  - [SDKAssistantMessage](#sdkassistantmessage)
  - [SDKUserMessage](#sdkusermessage)
  - [SDKUserMessageReplay](#sdkusermessagereplay)
  - [SDKResultMessage](#sdkresultmessage)
  - [user_message_uuid](#user_message_uuid)
  - [user_message_uuids](#user_message_uuids)
  - [resume_reason](#resume_reason)
  - [queued_turn_count](#queued_turn_count)
  - [startup_failure_reason](#startup_failure_reason)
  - [SDKSystemMessage](#sdksystemmessage)
  - [SDKPartialAssistantMessage](#sdkpartialassistantmessage)
  - [SDKCompactBoundaryMessage](#sdkcompactboundarymessage)
  - [SDKInformationalMessage](#sdkinformationalmessage)
  - [SDKWorkerShuttingDownMessage](#sdkworkershuttingdownmessage)
  - [SDKPluginInstallMessage](#sdkplugininstallmessage)
  - [SDKPermissionDeniedMessage](#sdkpermissiondeniedmessage)
  - [SDKPermissionDenial](#sdkpermissiondenial)
  - [SDKContextUsage](#sdkcontextusage)
  - [SDKContextUsageCategory](#sdkcontextusagecategory)
  - [SDKUsageReport](#sdkusagereport)
  - [SDKMessageOrigin](#sdkmessageorigin)
  - [Task-notification subkinds](#task-notification-subkinds)
  - [Declare a scheduled run](#declare-a-scheduled-run)
  - [Peer origin fields](#peer-origin-fields)
- [Hook Types](#hook-types)
  - [HookEvent](#hookevent)
  - [HookCallback](#hookcallback)
  - [HookCallbackMatcher](#hookcallbackmatcher)
  - [HookInput](#hookinput)
  - [BaseHookInput](#basehookinput)
  - [PreToolUseHookInput](#pretoolusehookinput)
  - [PostToolUseHookInput](#posttoolusehookinput)
  - [PostToolUseFailureHookInput](#posttoolusefailurehookinput)
  - [PostToolBatchHookInput](#posttoolbatchhookinput)
  - [PermissionDeniedHookInput](#permissiondeniedhookinput)
  - [NotificationHookInput](#notificationhookinput)
  - [UserPromptSubmitHookInput](#userpromptsubmithookinput)
  - [UserPromptExpansionHookInput](#userpromptexpansionhookinput)
  - [SessionStartHookInput](#sessionstarthookinput)
  - [SessionEndHookInput](#sessionendhookinput)
  - [StopHookInput](#stophookinput)
  - [StopFailureHookInput](#stopfailurehookinput)
  - [SubagentStartHookInput](#subagentstarthookinput)
  - [SubagentStopHookInput](#subagentstophookinput)
  - [PreCompactHookInput](#precompacthookinput)
  - [PostCompactHookInput](#postcompacthookinput)
  - [PreModelSwitchHookInput](#premodelswitchhookinput)
  - [PostModelSwitchHookInput](#postmodelswitchhookinput)
  - [PermissionRequestHookInput](#permissionrequesthookinput)
  - [SetupHookInput](#setuphookinput)
  - [TeammateIdleHookInput](#teammateidlehookinput)
  - [TaskCreatedHookInput](#taskcreatedhookinput)
  - [TaskCompletedHookInput](#taskcompletedhookinput)
  - [ElicitationHookInput](#elicitationhookinput)
  - [ElicitationResultHookInput](#elicitationresulthookinput)
  - [ConfigChangeHookInput](#configchangehookinput)
  - [InstructionsLoadedHookInput](#instructionsloadedhookinput)
  - [DirectoryAddedHookInput](#directoryaddedhookinput)
  - [WorktreeCreateHookInput](#worktreecreatehookinput)
  - [WorktreeRemoveHookInput](#worktreeremovehookinput)
  - [CwdChangedHookInput](#cwdchangedhookinput)
  - [FileChangedHookInput](#filechangedhookinput)
  - [MessageDisplayHookInput](#messagedisplayhookinput)
  - [HookJSONOutput](#hookjsonoutput)
  - [AsyncHookJSONOutput](#asynchookjsonoutput)
  - [SyncHookJSONOutput](#synchookjsonoutput)
- [Tool Input Types](#tool-input-types)
  - [ToolInputSchemas](#toolinputschemas)
  - [Agent](#agent)
  - [AskUserQuestion](#askuserquestion)
  - [Bash](#bash)
  - [Monitor](#monitor)
  - [TaskOutput](#taskoutput)
  - [Edit](#edit)
  - [Read](#read)
  - [Write](#write)
  - [Glob](#glob)
  - [Grep](#grep)
  - [TaskStop](#taskstop)
  - [NotebookEdit](#notebookedit)
  - [WebFetch](#webfetch)
  - [WebSearch](#websearch)
  - [Workflow](#workflow)
  - [TodoWrite](#todowrite)
  - [TaskCreate](#taskcreate)
  - [TaskUpdate](#taskupdate)
  - [TaskGet](#taskget)
  - [TaskList](#tasklist)
  - [ExitPlanMode](#exitplanmode)
  - [ListMcpResources](#listmcpresources)
  - [ReadMcpResource](#readmcpresource)
  - [EnterWorktree](#enterworktree)
  - [ExitWorktree](#exitworktree)
  - [EnterPlanMode](#enterplanmode)
  - [CronCreate](#croncreate)
  - [CronDelete](#crondelete)
  - [CronList](#cronlist)
  - [ScheduleWakeup](#schedulewakeup)
  - [RemoteTrigger](#remotetrigger)
  - [PushNotification](#pushnotification)
  - [REPL](#repl)
  - [ReportFindings](#reportfindings)
  - [Artifact](#artifact)
  - [Projects](#projects)
  - [ReadMcpResourceDir](#readmcpresourcedir)
  - [RefreshMcpTools](#refreshmcptools)
  - [ShowOnboardingRolePicker](#showonboardingrolepicker)
  - [McpInput](#mcpinput)
- [Tool Output Types](#tool-output-types)
  - [ToolOutputSchemas](#tooloutputschemas)
  - [Agent](#agent-2)
  - [AskUserQuestion](#askuserquestion-2)
  - [Bash](#bash-2)
  - [Monitor](#monitor-2)
  - [Edit](#edit-2)
  - [Read](#read-2)
  - [Write](#write-2)
  - [Glob](#glob-2)
  - [Grep](#grep-2)
  - [TaskStop](#taskstop-2)
  - [NotebookEdit](#notebookedit-2)
  - [WebFetch](#webfetch-2)
  - [WebSearch](#websearch-2)
  - [Workflow](#workflow-2)
  - [TodoWrite](#todowrite-2)
  - [TaskCreate](#taskcreate-2)
  - [TaskUpdate](#taskupdate-2)
  - [TaskGet](#taskget-2)
  - [TaskList](#tasklist-2)
  - [ExitPlanMode](#exitplanmode-2)
  - [ListMcpResources](#listmcpresources-2)
  - [ReadMcpResource](#readmcpresource-2)
  - [EnterWorktree](#enterworktree-2)
  - [ExitWorktree](#exitworktree-2)
  - [EnterPlanMode](#enterplanmode-2)
  - [CronCreate](#croncreate-2)
  - [CronDelete](#crondelete-2)
  - [CronList](#cronlist-2)
  - [ScheduleWakeup](#schedulewakeup-2)
  - [RemoteTrigger](#remotetrigger-2)
  - [PushNotification](#pushnotification-2)
  - [ReportFindings](#reportfindings-2)
  - [Artifact](#artifact-2)
  - [Projects](#projects-2)
  - [ReadMcpResourceDir](#readmcpresourcedir-2)
  - [RefreshMcpTools](#refreshmcptools-2)
  - [ShowOnboardingRolePicker](#showonboardingrolepicker-2)
  - [McpOutput](#mcpoutput)
- [Permission Types](#permission-types)
  - [PermissionUpdate](#permissionupdate)
  - [PermissionBehavior](#permissionbehavior)
  - [PermissionUpdateDestination](#permissionupdatedestination)
  - [PermissionRuleValue](#permissionrulevalue)
- [Other Types](#other-types)
  - [ApiKeySource](#apikeysource)
  - [SdkBeta](#sdkbeta)
  - [SlashCommand](#slashcommand)
  - [ModelInfo](#modelinfo)
  - [AgentInfo](#agentinfo)
  - [McpServerProvenance](#mcpserverprovenance)
  - [McpServerStatus](#mcpserverstatus)
  - [McpServerStatusConfig](#mcpserverstatusconfig)
  - [AccountInfo](#accountinfo)
  - [ModelUsage](#modelusage)
  - [ConfigScope](#configscope)
  - [NonNullableUsage](#nonnullableusage)
  - [Usage](#usage)
  - [CallToolResult](#calltoolresult)
  - [SDKMcpResourceLink](#sdkmcpresourcelink)
  - [ThinkingConfig](#thinkingconfig)
  - [SpawnedProcess](#spawnedprocess)
  - [SpawnOptions](#spawnoptions)
  - [McpSetServersResult](#mcpsetserversresult)
  - [RewindFilesResult](#rewindfilesresult)
  - [SDKStatusMessage](#sdkstatusmessage)
  - [SDKTaskNotificationMessage](#sdktasknotificationmessage)
  - [SDKToolUseSummaryMessage](#sdktoolusesummarymessage)
  - [SDKHookStartedMessage](#sdkhookstartedmessage)
  - [SDKHookProgressMessage](#sdkhookprogressmessage)
  - [SDKHookResponseMessage](#sdkhookresponsemessage)
  - [SDKToolProgressMessage](#sdktoolprogressmessage)
  - [SDKAuthStatusMessage](#sdkauthstatusmessage)
  - [SDKTaskStartedMessage](#sdktaskstartedmessage)
  - [SDKTaskProgressMessage](#sdktaskprogressmessage)
  - [SDKTaskUpdatedMessage](#sdktaskupdatedmessage)
  - [SDKBackgroundTasksChangedMessage](#sdkbackgroundtaskschangedmessage)
  - [SDKThinkingTokensMessage](#sdkthinkingtokensmessage)
  - [SDKSessionStateChangedMessage](#sdksessionstatechangedmessage)
  - [SDKFilesPersistedEvent](#sdkfilespersistedevent)
  - [SDKRateLimitEvent](#sdkratelimitevent)
  - [SDKLocalCommandOutputMessage](#sdklocalcommandoutputmessage)
  - [SDKCommandsChangedMessage](#sdkcommandschangedmessage)
  - [SDKPromptSuggestionMessage](#sdkpromptsuggestionmessage)
  - [SDKConversationResetMessage](#sdkconversationresetmessage)
  - [AbortError](#aborterror)
- [Sandbox Configuration](#sandbox-configuration)
  - [SandboxSettings](#sandboxsettings)
  - [Example usage](#example-usage)
  - [SandboxNetworkConfig](#sandboxnetworkconfig)
  - [SandboxFilesystemConfig](#sandboxfilesystemconfig)
  - [Permissions Fallback for Unsandboxed Commands](#permissions-fallback-for-unsandboxed-commands)
- [See also](#see-also)

SDK references

# Agent SDK reference - TypeScript

Copy pageCopy page

Complete API reference for the TypeScript Agent SDK, including all functions, types, and interfaces.

Copy pageCopy page


[​](#installation)

Installation

```python
npm install @anthropic-ai/claude-agent-sdk
```

The SDK bundles a native Claude Code binary for your platform as an optional dependency such as `@anthropic-ai/claude-agent-sdk-darwin-arm64`. Most installs need no separate Claude Code install. The SDK version tracks the bundled Claude Code version. SDK v0.3.191 bundles Claude Code v2.1.191, so a feature on this page that requires a Claude Code version needs the SDK release with the same patch number or later. If your package manager skips optional dependencies, the SDK throws `Native CLI binary for <platform>-<arch> not found`; set [`pathToClaudeCodeExecutable`](#options) to a separately installed `claude` binary instead.If your package manager doesn’t apply npm’s `libc` field, as Yarn 1.x doesn’t, you get both the glibc and musl platform packages on Linux, roughly doubling the install size. On Agent SDK v0.2.141 or later, the SDK still launches the correct variant. To reclaim the space in a container image, delete the platform package that doesn’t match the libc where your app runs; for a glibc runtime on x64, that’s `rm -rf node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl`. On a development machine the deletion is temporary, since Yarn reinstalls the package on the next dependency change.


[​](#compile-to-a-single-executable)

Compile to a single executable

When you compile your application into a single-file executable with `bun build --compile`, the SDK cannot resolve the bundled CLI binary at runtime. `require.resolve` does not work inside the compiled executable’s `$bunfs` virtual filesystem, so the SDK throws `Native CLI binary for <platform>-<arch> not found`. To work around this, embed the platform binary as a file asset, extract it to a real path at startup with `extractFromBunfs()`, and pass that path to [`pathToClaudeCodeExecutable`](#options). The `extractFromBunfs()` helper requires `@anthropic-ai/claude-agent-sdk` v0.3.144 or later. The example below builds for macOS on Apple Silicon:

```python
import binPath from "@anthropic-ai/claude-agent-sdk-darwin-arm64/claude" with { type: "file" };
import { extractFromBunfs } from "@anthropic-ai/claude-agent-sdk/extract";
import { query } from "@anthropic-ai/claude-agent-sdk";

const cliPath = extractFromBunfs(binPath);

for await (const message of query({
  prompt: "Hello",
  options: { pathToClaudeCodeExecutable: cliPath },
})) {
  console.log(message);
}
```

`extractFromBunfs()` copies the embedded binary out of the compiled executable’s virtual filesystem to a per-user temp directory and returns the real path. Outside a compiled executable it returns the input path unchanged, so the same code runs in development without modification. Each compiled executable embeds a single platform’s binary. Match the platform package in the import to your `--target`:

- To cross-compile, install the non-matching platform package, for example `npm install @anthropic-ai/claude-agent-sdk-linux-x64 --force`.
- On Windows, the binary subpath is `claude.exe`, for example `@anthropic-ai/claude-agent-sdk-win32-x64/claude.exe`.


[​](#import-the-/core-entry-when-you-bundle-the-agent-sdk)

Import the `/core` entry when you bundle the Agent SDK

If your application bundles the Agent SDK together with its own dependencies, import from `@anthropic-ai/claude-agent-sdk/core` instead of the package root. The `/core` entry requires TypeScript Agent SDK v0.3.282 or later, and its types require TypeScript 5.0 or later. The `/core` entry exports the same `query()`, `startup()`, `tool()`, `createSdkMcpServer()`, and `resolveSettings()` as the root entry, along with the functions that rename, tag, and delete sessions, `AbortError`, the runtime constants, and every type. It adds no names of its own. To keep the code your application loads small, `/core` leaves out some root exports, including `prewarm()`, the `InMemorySessionStore` class, and the helpers that list, read, fork, import, and summarize sessions. If you need one of them, use the root entry instead. The root entry inlines its own copies of `zod` and `@modelcontextprotocol/sdk`. The `/core` entry imports them from your `node_modules` at the ranges the Agent SDK’s `peerDependencies` declare, so a bundle that already includes them doesn’t carry a second copy. Import from either the root or `/core` in a given process, not both: they are separate bundles, and loading both gives you two copies of the Agent SDK’s classes and state.


[​](#functions)

Functions


[​](#query)

`query()`

The primary function for interacting with Claude Code. Creates an async generator that streams messages as they arrive.

```python
function query({
  prompt,
  options
}: {
  prompt: string | AsyncIterable<SDKUserMessage>;
  options?: Options;
}): Query;
```


[​](#parameters)

Parameters

| Parameter | Type                                                            | Description                                                       |
|:----------|:----------------------------------------------------------------|:------------------------------------------------------------------|
| `prompt`  | `string | AsyncIterable<`[`SDKUserMessage`](#sdkusermessage)`>` | The input prompt as a string or async iterable for streaming mode |
| `options` | [`Options`](#options)                                           | Optional configuration object (see Options type below)            |


[​](#returns)

Returns

Returns a [`Query`](#query-object) object that extends `AsyncGenerator<`[`SDKMessage`](#sdkmessage)`, void>` with additional methods.


[​](#startup)

`startup()`

Pre-warms the CLI subprocess by spawning it and completing the initialize handshake before a prompt is available. The returned [`WarmQuery`](#warmquery) handle accepts a prompt later and writes it to an already-ready process, so the first `query()` call resolves without paying subprocess spawn and initialization cost inline. If you don’t know the session’s working directory yet, use [`prewarm()`](#prewarm) instead.

```python
function startup(params?: {
  options?: Options;
  initializeTimeoutMs?: number;
}): Promise<WarmQuery>;
```


[​](#parameters-2)

Parameters

| Parameter             | Type                  | Description                                                                                                                                                                    |
|:----------------------|:----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `options`             | [`Options`](#options) | Optional configuration object. Same as the `options` parameter to `query()`                                                                                                    |
| `initializeTimeoutMs` | `number`              | Maximum time in milliseconds to wait for subprocess initialization. Defaults to `60000`. If initialization does not complete in time, the promise rejects with a timeout error |


[​](#returns-2)

Returns

Returns a `Promise<`[`WarmQuery`](#warmquery)`>` that resolves once the subprocess has spawned and completed its initialize handshake.


[​](#example)

Example

Call `startup()` early, for example on application boot, then call `.query()` on the returned handle once a prompt is ready. This moves subprocess spawn and initialization out of the critical path.

```python
import { startup } from "@anthropic-ai/claude-agent-sdk";

// Pay startup cost upfront
const warm = await startup({ options: { maxTurns: 3 } });

// Later, when a prompt is ready, this is immediate
for await (const message of warm.query("What files are here?")) {
  console.log(message);
}
```


[​](#prewarm)

`prewarm()`

*Alpha.* Starts a Claude Code process as a spare before you know which session it will serve, so you can bind it to a session later with [`claim()`](#spareprocess). Use it in an application that boots before the user picks a folder. Requires TypeScript Agent SDK v0.3.282 or later. `prewarm()` completes the same initialize handshake as [`startup()`](#startup), with the process waiting in `options.cwd` when you set it and otherwise in a private temporary directory under your Claude Code config directory. The session’s working directory, its `SessionStart` hooks, its stdio MCP servers, and its CLAUDE.md and git context wait for the claim. A spare holds roughly 230 to 260 MB of memory while it waits. If your [`spawnClaudeCodeProcess`](#options) runs Claude Code on another machine or in a container, set `options.cwd` to a directory that exists there for the spare to wait in.

```python
function prewarm(params?: {
  options?: Options;
  initializeTimeoutMs?: number;
}): Promise<SpareProcess>;
```

`options` and `initializeTimeoutMs` mean the same as for `startup()`, except that `options.cwd` sets only the directory the spare waits in. The promise resolves with a [`SpareProcess`](#spareprocess) once the process has completed its initialize handshake. `prewarm()` throws if `options` sets `resume`, `continue`, or `forkSession`, because a spare has no session yet. Everything a claim can’t set, such as `mcpServers`, `hooks`, `canUseTool`, `settingSources`, `systemPrompt`, and `plugins`, is fixed for the life of the spare, so keep one spare per distinct set of those options and prewarm again when they change.


[​](#example-2)

Example

Prewarm on application boot, then claim the spare when the user starts a session:

```python
import { prewarm } from "@anthropic-ai/claude-agent-sdk";

// On application boot, before the session's folder is known
const spare = await prewarm({ options: { maxTurns: 3 } });

// Later, when the user starts a session in a folder
const claimedQuery = spare.claim({
  prompt: "What files are here?",
  options: { cwd: "/path/to/project" },
});

spare.claimed.catch((error: Error) => {
  // Unless the message starts with "option_not_applied", the prompt didn't run:
  // start this session with query() instead
  console.error("Claim failed:", error.message);
});

try {
  for await (const message of claimedQuery) {
    console.log(message);
  }
} catch (error) {
  // After a refused claim, the claimed query throws once it has yielded the error result
  console.error(`Session ended with an error: ${error}`);
}
```


[​](#tool)

`tool()`

Creates a type-safe MCP tool definition for use with SDK MCP servers.

```python
function tool<Schema extends AnyZodRawShape>(
  name: string,
  description: string,
  inputSchema: Schema,
  handler: (args: InferShape<Schema>, extra: unknown) => Promise<CallToolResult>,
  extras?: { annotations?: ToolAnnotations; searchHint?: string; alwaysLoad?: boolean }
): SdkMcpToolDefinition<Schema>;
```


[​](#parameters-3)

Parameters

| Parameter     | Type                                                                                                   | Description                                                                                                                                                                                                                                                                                                        |
|:--------------|:-------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name`        | `string`                                                                                               | The name of the tool                                                                                                                                                                                                                                                                                               |
| `description` | `string`                                                                                               | A description of what the tool does                                                                                                                                                                                                                                                                                |
| `inputSchema` | `Schema extends AnyZodRawShape`                                                                        | Zod schema defining the tool’s input parameters (supports both Zod 3 and Zod 4)                                                                                                                                                                                                                                    |
| `handler`     | `(args, extra) => Promise<`[`CallToolResult`](#calltoolresult)`>`                                      | Async function that executes the tool logic                                                                                                                                                                                                                                                                        |
| `extras`      | `{ annotations?: `[`ToolAnnotations`](#toolannotations)`; searchHint?: string; alwaysLoad?: boolean }` | Optional extras. `annotations` provides MCP behavioral hints to clients. `searchHint` is a one-line capability phrase shown in the deferred-tool list when [tool search](agent-sdk-tool-search.md) is active. `alwaysLoad: true` keeps this tool’s full schema in the initial prompt instead of deferring it |


[​](#toolannotations)

`ToolAnnotations`

Defined in `@modelcontextprotocol/sdk/types.js`. All fields are optional hints; clients should not rely on them for security decisions.

| Field             | Type      | Default     | Description                                                                                                                                          |
|:------------------|:----------|:------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `title`           | `string`  | `undefined` | Human-readable title for the tool                                                                                                                    |
| `readOnlyHint`    | `boolean` | `false`     | If `true`, the tool does not modify its environment                                                                                                  |
| `destructiveHint` | `boolean` | `true`      | If `true`, the tool may perform destructive updates (only meaningful when `readOnlyHint` is `false`)                                                 |
| `idempotentHint`  | `boolean` | `false`     | If `true`, repeated calls with the same arguments have no additional effect (only meaningful when `readOnlyHint` is `false`)                         |
| `openWorldHint`   | `boolean` | `true`      | If `true`, the tool interacts with external entities (for example, web search). If `false`, the tool’s domain is closed (for example, a memory tool) |

```python
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const searchTool = tool(
  "search",
  "Search the web",
  { query: z.string() },
  async ({ query }) => {
    return { content: [{ type: "text", text: `Results for: ${query}` }] };
  },
  { annotations: { readOnlyHint: true, openWorldHint: true } }
);
```


[​](#createsdkmcpserver)

`createSdkMcpServer()`

Creates an MCP server instance that runs in the same process as your application.

```python
function createSdkMcpServer(options: {
  name: string;
  version?: string;
  instructions?: string;
  tools?: Array<SdkMcpToolDefinition<any>>;
  alwaysLoad?: boolean;
  timeout?: number;
}): McpSdkServerConfigWithInstance;
```


[​](#parameters-4)

Parameters

| Parameter              | Type                          | Description                                                                                                                                                                                                                                                              |
|:-----------------------|:------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `options.name`         | `string`                      | The name of the MCP server                                                                                                                                                                                                                                               |
| `options.version`      | `string`                      | Optional version string                                                                                                                                                                                                                                                  |
| `options.instructions` | `string`                      | Optional server instructions, returned from `initialize` and surfaced to the model as an MCP instructions block                                                                                                                                                          |
| `options.tools`        | `Array<SdkMcpToolDefinition>` | Array of tool definitions created with [`tool()`](#tool)                                                                                                                                                                                                                 |
| `options.alwaysLoad`   | `boolean`                     | When `true`, this server’s tools stay in the initial prompt instead of being deferred behind [tool search](agent-sdk-tool-search.md). Combines with per-tool `alwaysLoad` in [`tool()`](#tool)                                                                     |
| `options.timeout`      | `number`                      | Timeout in milliseconds for this server’s tool calls. Claude Code applies it to this server in place of [`MCP_TOOL_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md). Pass a whole number of at least 1000. Claude Code ignores other values. Requires TypeScript Agent SDK v0.3.248 or later |


[​](#listsessions)

`listSessions()`

Discovers and lists past sessions with light metadata. Filter by project directory or list sessions across all projects.

```python
function listSessions(options?: ListSessionsOptions): Promise<SDKSessionInfo[]>;
```


[​](#parameters-5)

Parameters

| Parameter                  | Type      | Default     | Description                                                                        |
|:---------------------------|:----------|:------------|:-----------------------------------------------------------------------------------|
| `options.dir`              | `string`  | `undefined` | Directory to list sessions for. When omitted, returns sessions across all projects |
| `options.limit`            | `number`  | `undefined` | Maximum number of sessions to return                                               |
| `options.includeWorktrees` | `boolean` | `true`      | When `dir` is inside a git repository, include sessions from all worktree paths    |


[​](#return-type-sdksessioninfo)

Return type: `SDKSessionInfo`

| Property       | Type                 | Description                                                                                                                                                                                                                |
|:---------------|:---------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `sessionId`    | `string`             | Unique session identifier (UUID)                                                                                                                                                                                           |
| `summary`      | `string`             | Display title: custom title, most recent prompt, auto-generated summary, or first prompt                                                                                                                                   |
| `lastModified` | `number`             | Last modified time in milliseconds since epoch                                                                                                                                                                             |
| `fileSize`     | `number | undefined` | Session file size in bytes. Only populated for local JSONL storage                                                                                                                                                         |
| `customTitle`  | `string | undefined` | The session’s custom title when one is set, for example with `--name`, `/rename`, a hook’s `sessionTitle` output, or [`renameSession()`](#renamesession). Otherwise the AI-generated session title, if the session has one |
| `firstPrompt`  | `string | undefined` | First meaningful user prompt in the session                                                                                                                                                                                |
| `gitBranch`    | `string | undefined` | Git branch at the end of the session                                                                                                                                                                                       |
| `cwd`          | `string | undefined` | Working directory for the session                                                                                                                                                                                          |
| `tag`          | `string | undefined` | User-set session tag (see [`tagSession()`](#tagsession))                                                                                                                                                                   |
| `createdAt`    | `number | undefined` | Creation time in milliseconds since epoch, from the first entry’s timestamp                                                                                                                                                |


[​](#example-3)

Example

Print the 10 most recent sessions for a project. Results are sorted by `lastModified` descending, so the first item is the newest. Omit `dir` to search across all projects.

```python
import { listSessions } from "@anthropic-ai/claude-agent-sdk";

const sessions = await listSessions({ dir: "/path/to/project", limit: 10 });

for (const session of sessions) {
  console.log(`${session.summary} (${session.sessionId})`);
}
```


[​](#getsessionmessages)

`getSessionMessages()`

Reads user and assistant messages from a past session transcript.

```python
function getSessionMessages(
  sessionId: string,
  options?: GetSessionMessagesOptions
): Promise<SessionMessage[]>;
```


[​](#parameters-6)

Parameters

| Parameter        | Type     | Default     | Description                                                                   |
|:-----------------|:---------|:------------|:------------------------------------------------------------------------------|
| `sessionId`      | `string` | required    | Session UUID to read (see `listSessions()`)                                   |
| `options.dir`    | `string` | `undefined` | Project directory to find the session in. When omitted, searches all projects |
| `options.limit`  | `number` | `undefined` | Maximum number of messages to return                                          |
| `options.offset` | `number` | `undefined` | Number of messages to skip from the start                                     |


[​](#return-type-sessionmessage)

Return type: `SessionMessage`

| Property             | Type                   | Description                                                                                                                                                                                                                                                                        |
|:---------------------|:-----------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type`               | `"user" | "assistant"` | Message role                                                                                                                                                                                                                                                                       |
| `uuid`               | `string`               | Unique message identifier                                                                                                                                                                                                                                                          |
| `session_id`         | `string`               | Session this message belongs to                                                                                                                                                                                                                                                    |
| `message`            | `unknown`              | Raw message payload from the transcript                                                                                                                                                                                                                                            |
| `parent_tool_use_id` | `string | null`        | For subagent messages, the `tool_use_id` of the `Agent` or `Skill` tool call that started the subagent. `null` for main-session messages and older sessions                                                                                                                        |
| `parent_agent_id`    | `string | null`        | For messages from a [nested subagent](../09-Agents-Patterns/sub-agents.md#let-subagents-spawn-their-own-subagents), the `agentId` of the subagent that spawned it. `null` for main-session messages, messages from top-level subagents, and older sessions. Requires Claude Code v2.1.202 or later |


[​](#example-4)

Example

```python
import { listSessions, getSessionMessages } from "@anthropic-ai/claude-agent-sdk";

const [latest] = await listSessions({ dir: "/path/to/project", limit: 1 });

if (latest) {
  const messages = await getSessionMessages(latest.sessionId, {
    dir: "/path/to/project",
    limit: 20
  });

  for (const msg of messages) {
    console.log(`[${msg.type}] ${msg.uuid}`);
  }
}
```


[​](#getsessioninfo)

`getSessionInfo()`

Reads metadata for a single session by ID without scanning the full project directory.

```python
function getSessionInfo(
  sessionId: string,
  options?: GetSessionInfoOptions
): Promise<SDKSessionInfo | undefined>;
```


[​](#parameters-7)

Parameters

| Parameter     | Type     | Default     | Description                                                            |
|:--------------|:---------|:------------|:-----------------------------------------------------------------------|
| `sessionId`   | `string` | required    | UUID of the session to look up                                         |
| `options.dir` | `string` | `undefined` | Project directory path. When omitted, searches all project directories |

Returns [`SDKSessionInfo`](#return-type-sdksessioninfo), or `undefined` if the session is not found.


[​](#renamesession)

`renameSession()`

Renames a session by appending a custom-title entry. Repeated calls are safe; the most recent title wins.

```python
function renameSession(
  sessionId: string,
  title: string,
  options?: SessionMutationOptions
): Promise<void>;
```


[​](#parameters-8)

Parameters

| Parameter     | Type     | Default     | Description                                                            |
|:--------------|:---------|:------------|:-----------------------------------------------------------------------|
| `sessionId`   | `string` | required    | UUID of the session to rename                                          |
| `title`       | `string` | required    | New title. Must be non-empty after trimming whitespace                 |
| `options.dir` | `string` | `undefined` | Project directory path. When omitted, searches all project directories |


[​](#tagsession)

`tagSession()`

Tags a session. Pass `null` to clear the tag. Repeated calls are safe; the most recent tag wins.

```python
function tagSession(
  sessionId: string,
  tag: string | null,
  options?: SessionMutationOptions
): Promise<void>;
```


[​](#parameters-9)

Parameters

| Parameter     | Type            | Default     | Description                                                            |
|:--------------|:----------------|:------------|:-----------------------------------------------------------------------|
| `sessionId`   | `string`        | required    | UUID of the session to tag                                             |
| `tag`         | `string | null` | required    | Tag string, or `null` to clear                                         |
| `options.dir` | `string`        | `undefined` | Project directory path. When omitted, searches all project directories |


[​](#resolvesettings)

`resolveSettings()`

Resolves the effective Claude Code settings for a given directory using the same merge engine as the CLI, without spawning the Claude CLI. Use it to inspect what configuration a `query()` call would see before invoking one.

This function is alpha and its API may change before stabilization.

The snapshot differs from what a live `query()` session applies:

- **`policyHelper`**: `resolveSettings()` reads MDM sources, including macOS plist and Windows HKLM/HKCU, but doesn’t execute the admin-configured `policyHelper` subprocess.
- **Server-managed settings**: `resolveSettings()` doesn’t fetch [server-managed settings](../13-Enterprise-Admin/server-managed-settings.md#fetch-and-caching-behavior). Pass them as `options.serverManagedSettings` to include them.
- **`defaultMode`**: the snapshot returns `permissions.defaultMode` as-is from every tier, so it can include the `'auto'` and `'bypassPermissions'` values from project and local settings, which [a live session ignores](../02-Claude-Code-CLI/permission-modes.md#which-mode-a-session-starts-in).

```python
function resolveSettings(
  options?: ResolveSettingsOptions
): Promise<ResolvedSettings>;
```


[​](#parameters-10)

Parameters

`resolveSettings()` accepts a single options object. All fields are optional.

| Parameter                       | Type                                  | Default         | Description                                                                                                                                                                                                                                                                                                        |
|:--------------------------------|:--------------------------------------|:----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `options.cwd`                   | `string`                              | `process.cwd()` | Directory to resolve project and local settings relative to                                                                                                                                                                                                                                                        |
| `options.settingSources`        | [`SettingSource`](#settingsource)`[]` | All sources     | Which filesystem sources to load. Pass `[]` to skip user, project, and local settings. [Endpoint-managed policy](../13-Enterprise-Admin/managed-settings.md#delivery-mechanisms) loads in all cases. `resolveSettings()` includes server-managed settings only when you pass `options.serverManagedSettings`                        |
| `options.managedSettings`       | `Settings`                            | `undefined`     | Policy-tier settings supplied by the embedding host. Follows the same rules as [`managedSettings` in `Options`](#options), except that `resolveSettings()` doesn’t execute a configured [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper), so the snapshot can include settings that a live session drops |
| `options.serverManagedSettings` | `Settings`                            | `undefined`     | Server-managed settings payload from `/api/claude_code/settings`. Non-restrictive keys pass through unfiltered                                                                                                                                                                                                     |


[​](#return-type-resolvedsettings)

Return type: `ResolvedSettings`

`resolveSettings()` returns an object describing the merged settings and the source that contributed each key.

| Property     | Type                                                | Description                                                            |
|:-------------|:----------------------------------------------------|:-----------------------------------------------------------------------|
| `effective`  | `Settings`                                          | Merged settings after applying all enabled sources in precedence order |
| `provenance` | `Partial<Record<keyof Settings, ProvenanceEntry>>`  | For each top-level key in `effective`, which source supplied the value |
| `sources`    | `Array<{ source, settings, path?, policyOrigin? }>` | Per-source raw settings, ordered from lowest to highest precedence     |


[​](#example-5)

Example

The example below resolves settings for a project directory and prints the source that controls the cleanup period. On a machine where no settings file sets `cleanupPeriodDays`, both printed lines show `undefined` for the value, which is the expected output rather than an error.

```python
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";

const { effective, provenance } = await resolveSettings({
  cwd: "/path/to/project",
  settingSources: ["user", "project", "local"],
});

console.log(`Cleanup period: ${effective.cleanupPeriodDays} days`);
console.log(`Set by: ${provenance.cleanupPeriodDays?.source}`);
```


[​](#types)

Types


[​](#options)

`Options`

Configuration object for the `query()` function.

| Property                          | Type                                                                                                                                                                                                       | Default                                     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|:----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `abortController`                 | `AbortController`                                                                                                                                                                                          | `new AbortController()`                     | Controller for cancelling operations                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `additionalDirectories`           | `string[]`                                                                                                                                                                                                 | `[]`                                        | Additional directories Claude can access. The SDK passes each entry to Claude Code as `--add-dir`, so with the `project` setting source Claude Code also [loads the directory’s skills, commands, and subagents](../02-Claude-Code-CLI/permissions.md#additional-directories-grant-file-access-not-configuration)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `agent`                           | `string`                                                                                                                                                                                                   | `undefined`                                 | Agent name for the main thread. The agent must be defined in the `agents` option or in settings                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `agents`                          | `Record<string, [`AgentDefinition`](#agentdefinition)>`                                                                                                                                                    | `undefined`                                 | Programmatically define subagents                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `agentProgressSummaries`          | `boolean`                                                                                                                                                                                                  | `false`                                     | When `true`, generate one-line progress summaries for subagents and forward them on [`task_progress`](#sdktaskprogressmessage) events via the `summary` field. Applies to foreground and background subagents                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `allowDangerouslySkipPermissions` | `boolean`                                                                                                                                                                                                  | `false`                                     | Enable bypassing permissions. Required when using `permissionMode: 'bypassPermissions'`, at startup or later through `setPermissionMode()`. See [plan mode](agent-sdk-permissions.md#plan-mode-plan) for how it interacts with `permissionMode: 'plan'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `allowedTools`                    | `string[]`                                                                                                                                                                                                 | `[]`                                        | Tools to auto-approve without prompting. This does not restrict Claude to only these tools. If you name one of the [task-tracking tools](agent-sdk-todo-tracking.md#model-availability) here, Claude Code also opts the session in. Other unlisted tools fall through to `permissionMode` and `canUseTool`. Use `disallowedTools` to block tools. See [Permissions](agent-sdk-permissions.md#allow-and-deny-rules)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `betas`                           | [`SdkBeta`](#sdkbeta)`[]`                                                                                                                                                                                  | `[]`                                        | Enable beta features                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `canUseTool`                      | [`CanUseTool`](#canusetool)                                                                                                                                                                                | `undefined`                                 | Custom permission function, invoked only when the [permission flow](agent-sdk-permissions.md#how-permissions-are-evaluated) falls through to a prompt. Not invoked for calls auto-approved by `allowedTools`, allow rules, or `permissionMode`. An allow rule doesn’t pre-approve the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves). See [`CanUseTool`](#canusetool) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `continue`                        | `boolean`                                                                                                                                                                                                  | `false`                                     | Continue the most recent conversation                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `cwd`                             | `string`                                                                                                                                                                                                   | `process.cwd()`                             | Current working directory                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `debug`                           | `boolean`                                                                                                                                                                                                  | `false`                                     | Enable debug mode for the Claude Code process                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `debugFile`                       | `string`                                                                                                                                                                                                   | `undefined`                                 | Write debug logs to a specific file path. Implicitly enables debug mode                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `disallowedTools`                 | `string[]`                                                                                                                                                                                                 | `[]`                                        | Tools to deny. A bare name such as `"Bash"` removes the tool from Claude’s context. A scoped rule such as `"Bash(rm *)"` leaves the tool available and denies matching calls in every permission mode, including `bypassPermissions`, for the command [as written](../02-Claude-Code-CLI/permissions.md#bash-rule-limits). See [Permissions](agent-sdk-permissions.md#allow-and-deny-rules)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `effort`                          | `'low' | 'medium' | 'high' | 'xhigh' | 'max'`                                                                                                                                                              | `undefined`                                 | Controls how much effort Claude puts into its response. Works with adaptive thinking to guide thinking depth. See [adjust the effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `enableFileCheckpointing`         | `boolean`                                                                                                                                                                                                  | `false`                                     | Enable file change tracking for rewinding. See [File checkpointing](agent-sdk-file-checkpointing.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `env`                             | `Record<string, string | undefined>`                                                                                                                                                                       | `process.env`                               | Environment variables. When set, this replaces the subprocess environment instead of merging with `process.env`, so pass `{ ...process.env, YOUR_VAR: 'value' }` to keep inherited variables like `PATH`. See [Handle slow or stalled API responses](#handle-slow-or-stalled-api-responses) for an example of this pattern, and [Environment variables](../02-Claude-Code-CLI/env-vars.md) for variables the underlying CLI reads. Set `CLAUDE_AGENT_SDK_CLIENT_APP` to identify your app in the User-Agent header                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `executable`                      | `'bun' | 'deno' | 'node'`                                                                                                                                                                                  | Auto-detected                               | JavaScript runtime to use                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `executableArgs`                  | `string[]`                                                                                                                                                                                                 | `[]`                                        | Arguments to pass to the executable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `extraArgs`                       | `Record<string, string | null>`                                                                                                                                                                            | `{}`                                        | Additional arguments                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `fallbackModel`                   | `string`                                                                                                                                                                                                   | `undefined`                                 | Model to use if the primary model fails. Accepts a comma-separated list. For the order and the cap, see [Fallback model chains](../02-Claude-Code-CLI/model-config.md#fallback-model-chains). For guidance, see [Choose a model](agent-sdk-configuration.md#choose-a-model)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `forkSession`                     | `boolean`                                                                                                                                                                                                  | `false`                                     | When resuming with `resume`, fork to a new session ID instead of continuing the original session                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `forwardSubagentText`             | `boolean`                                                                                                                                                                                                  | `false`                                     | Forward subagent text and thinking blocks as assistant and user messages with `parent_tool_use_id` set, so consumers can render a nested transcript. Without this option, Claude Code omits the text and thinking blocks of a subagent that runs in the [foreground](../09-Agents-Patterns/sub-agents.md#run-subagents-in-foreground-or-background). For nested subagents, skills with `context: fork`, and the Claude Code version each needs, see [Follow subagent messages](../02-Claude-Code-CLI/headless.md#follow-subagent-messages)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `hooks`                           | `Partial<Record<`[`HookEvent`](#hookevent)`, `[`HookCallbackMatcher`](#hookcallbackmatcher)`[]>>`                                                                                                          | `{}`                                        | Hook callbacks for events                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `includeHookEvents`               | `boolean`                                                                                                                                                                                                  | `false`                                     | Include hook lifecycle events in the message stream as [`SDKHookStartedMessage`](#sdkhookstartedmessage), [`SDKHookProgressMessage`](#sdkhookprogressmessage), and [`SDKHookResponseMessage`](#sdkhookresponsemessage). Lifecycle events for `SessionStart` and `Setup` hooks are always included and don’t need this option. Some hook events, such as `Notification`, `SessionEnd`, `PreCompact`, and `PostCompact`, never produce an `SDKHookStartedMessage`, even with this option. For those events, Claude Code still emits an `SDKHookProgressMessage` while a command hook that runs for more than a second produces output, and emits an `SDKHookResponseMessage` only when a hook [that runs in the background](../07-Hooks/hooks.md#run-hooks-in-the-background) finishes                                                                                                                                                                                                                                                                                                                                                                                                       |
| `includePartialMessages`          | `boolean`                                                                                                                                                                                                  | `false`                                     | Include partial message events                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `loadTimeoutMs`                   | `number`                                                                                                                                                                                                   | `60000`                                     | *Alpha.* Timeout in milliseconds for each `sessionStore.load()` and `sessionStore.listSubkeys()` call during resume materialization. If the adapter doesn’t settle within this window, the query fails instead of hanging. Ignored when `sessionStore` is not set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `managedSettings`                 | `Settings`                                                                                                                                                                                                 | `undefined`                                 | Policy-tier settings your host process supplies to the spawned session. On machines with admin-deployed managed settings, Claude Code ignores these unless the admin’s highest-priority managed source sets `parentSettingsBehavior: 'merge'`, and never merges them while a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) supplies managed settings. Merged values pass through a restrictive-only filter; [Restrict parent settings](../13-Enterprise-Admin/claude-apps-gateway.md#restrict-parent-settings) covers what the filter admits and the `allowManaged*Only` locks. A host that sets [`CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`](../02-Claude-Code-CLI/env-vars.md) has three keys read straight from this payload instead: its [model configuration](../02-Claude-Code-CLI/model-config.md#restrict-model-selection) on Claude Code v2.1.222 or later, [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) when no managed source sets it on v2.1.246 or later, and its `ENABLE_TOOL_SEARCH` env entry on v2.1.247 or later                                                                                                                                                                             |
| `maxBudgetUsd`                    | `number`                                                                                                                                                                                                   | `undefined`                                 | Stop the query when the client-side cost estimate reaches this USD value. The estimate can pass this value, so [leave headroom](agent-sdk-agent-loop.md#budget-headroom). Counts only the call’s own spend; totals restored from a resumed session don’t count. For accuracy caveats and reset behavior, see [Track cost and usage](agent-sdk-cost-tracking.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `maxThinkingTokens`               | `number`                                                                                                                                                                                                   | `undefined`                                 | *Deprecated:* Use `thinking` instead. Maximum tokens for thinking process                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `maxTurns`                        | `number`                                                                                                                                                                                                   | `undefined`                                 | Maximum agentic turns (tool-use round trips)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `mcpServers`                      | `Record<string, [`McpServerConfig`](#mcpserverconfig)>`                                                                                                                                                    | `{}`                                        | MCP server configurations                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `model`                           | `string`                                                                                                                                                                                                   | Default from CLI                            | Claude model alias or full model name. See [accepted values and provider-specific IDs](../02-Claude-Code-CLI/model-config.md#available-models)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `onElicitation`                   | `(request: ElicitationRequest, options: { signal: AbortSignal }) => Promise<ElicitationResult>`                                                                                                            | `undefined`                                 | Callback for handling MCP elicitation requests. Called when an MCP server requests user input and no hook handles it first. When not provided, unhandled elicitation requests are declined automatically                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `outputFormat`                    | `{ type: 'json_schema', schema: JSONSchema }`                                                                                                                                                              | `undefined`                                 | Define output format for agent results. See [Structured outputs](agent-sdk-structured-outputs.md) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `outputStyle`                     | `string`                                                                                                                                                                                                   | `undefined`                                 | Not an `Options` field. Set `outputStyle` in the inline [`settings`](../02-Claude-Code-CLI/settings.md) object or a settings file instead. See [Activate an output style](agent-sdk-modifying-system-prompts.md#activate-an-output-style)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `pathToClaudeCodeExecutable`      | `string`                                                                                                                                                                                                   | Auto-resolved from bundled native binary    | Path to Claude Code executable. Only needed if optional dependencies were skipped during install or your platform isn’t in the supported set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `permissionMode`                  | [`PermissionMode`](#permissionmode)                                                                                                                                                                        | `undefined`                                 | Permission mode for the session. If you omit it, the session can start in auto mode. See [Permission modes](agent-sdk-permissions.md#permission-modes) for how Claude Code picks the starting permission mode                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `permissionPromptToolName`        | `string`                                                                                                                                                                                                   | `undefined`                                 | MCP tool name for permission prompts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `permissionPrompts`               | `'host' | 'none'`                                                                                                                                                                                          | `'host'`                                    | Who answers permission prompts: `'host'` routes them to your [`canUseTool`](#canusetool) callback or the `permissionPromptToolName` tool, and `'none'` [denies the calls that would have prompted](agent-sdk-permissions.md#how-permissions-are-evaluated). Requires Claude Code v2.1.259 or later                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `persistSession`                  | `boolean`                                                                                                                                                                                                  | `true`                                      | When `false`, disables session persistence to disk. Sessions cannot be resumed later                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `planModeInstructions`            | `string`                                                                                                                                                                                                   | `undefined`                                 | Custom workflow instructions for plan mode. When `permissionMode` is `'plan'`, this string replaces the default plan-mode workflow body. The CLI still wraps it with the read-only enforcement preamble and the ExitPlanMode protocol footer                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `plugins`                         | [`SdkPluginConfig`](#sdkpluginconfig)`[]`                                                                                                                                                                  | `[]`                                        | Load custom plugins from local paths. See [Plugins](agent-sdk-plugins.md) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `projectConfigRoot`               | `string`                                                                                                                                                                                                   | `undefined`                                 | Absolute path of the trusted checkout that `cwd` is a worktree of. Claude Code reads project settings, `.mcp.json`, and the project’s `.claude/` commands, agents, skills, workflows, routines, and output styles from this directory instead of `cwd`, and sets `CLAUDE_PROJECT_DIR` to it. Hooks, helper scripts such as `apiKeyHelper`, and stdio MCP servers start with this directory as their working directory. `CLAUDE.md` files and `.claude/rules/` still load from `cwd`. Requires Claude Code v2.1.275 or later                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `promptSuggestions`               | `boolean`                                                                                                                                                                                                  | `false`                                     | Enable prompt suggestions. After a turn, Claude Code emits a `prompt_suggestion` message carrying a predicted next user prompt. Claude Code generates no suggestion for some turns, such as while your account is close to or at its usage limit. See [When Claude Code skips suggestions](../02-Claude-Code-CLI/interactive-mode.md#when-claude-code-skips-suggestions)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `resume`                          | `string`                                                                                                                                                                                                   | `undefined`                                 | Session ID to resume                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `resumeDropsTurn`                 | `string`                                                                                                                                                                                                   | `undefined`                                 | With `resumeSessionAt`: the prompt UUID of the turn the truncating resume intends to discard. Claude Code refuses the resume when the discarded range contains anything not attributable to that turn, such as absorbed queued messages or task notifications, and names the `--resume-drops-turn` flag in the rejection message. Only the Agent SDK and print-mode resumes read the pair. Requires Claude Code v2.1.223 or later                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `resumeSessionAt`                 | `string`                                                                                                                                                                                                   | `undefined`                                 | Resume session at a specific message UUID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `sandbox`                         | [`SandboxSettings`](#sandboxsettings)                                                                                                                                                                      | `undefined`                                 | Configure sandbox behavior programmatically. See [Sandbox settings](#sandboxsettings) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `sessionId`                       | `string`                                                                                                                                                                                                   | Auto-generated                              | Use a specific UUID for the session instead of auto-generating one                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `sessionStore`                    | [`SessionStore`](agent-sdk-session-storage.md#the-sessionstore-interface)                                                                                                                            | `undefined`                                 | Mirror session transcripts to an external backend so another host can resume them. See [Persist sessions to external storage](agent-sdk-session-storage.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `sessionStoreFlush`               | `'batched' | 'eager'`                                                                                                                                                                                      | `'batched'`                                 | *Alpha.* Flush mode for `sessionStore`. Ignored when `sessionStore` is not set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `settings`                        | `string | Settings`                                                                                                                                                                                        | `undefined`                                 | Inline [settings](../02-Claude-Code-CLI/settings.md) object, a settings file path, or an inline JSON string. Populates the flag-settings layer in the [precedence order](../02-Claude-Code-CLI/settings.md#settings-precedence). Change at runtime with [`applyFlagSettings()`](#applyflagsettings)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `settingSources`                  | [`SettingSource`](#settingsource)`[]`                                                                                                                                                                      | CLI defaults (all sources)                  | Control which filesystem settings to load. Pass `[]` to disable user, project, and local settings. [Endpoint-managed policy](../13-Enterprise-Admin/managed-settings.md#delivery-mechanisms) loads regardless; server-managed settings are fetched when the session authenticates with an organization credential on an [eligible configuration](../13-Enterprise-Admin/server-managed-settings.md#platform-availability). See [Use Claude Code features](agent-sdk-claude-code-features.md#what-settingsources-does-not-control)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `skills`                          | `string[] | 'all'`                                                                                                                                                                                         | `undefined`                                 | Skills available to the session. Pass `'all'` to enable every discovered skill, or a list of skill names. Pass exact names only. On Agent SDK v0.3.221 or later, the SDK rejects malformed and wildcard-form names with an error before starting the Claude Code process. When set, the SDK adds the Skill tool to `allowedTools` automatically. If you also pass `tools`, include `'Skill'` in that list. See [Skills](agent-sdk-skills.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `spawnClaudeCodeProcess`          | `(options: SpawnOptions) => SpawnedProcess`                                                                                                                                                                | `undefined`                                 | Custom function to spawn the Claude Code process. Use to run Claude Code in VMs, containers, or remote environments                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `stderr`                          | `(data: string) => void`                                                                                                                                                                                   | `undefined`                                 | Callback for stderr output                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `strictMcpConfig`                 | `boolean`                                                                                                                                                                                                  | `false`                                     | Use only the servers passed in `mcpServers` and ignore project `.mcp.json`, user settings, plugin-provided MCP servers, and [claude.ai connectors](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `systemPrompt`                    | `string | string[] | { type: 'custom'; prompt: string | string[]; snapshot?: boolean } | { type: 'preset'; preset: 'claude_code'; append?: string; excludeDynamicSections?: boolean; snapshot?: boolean }` | `undefined` (minimal prompt)                | System prompt configuration. Pass a string for a custom prompt, or `{ type: 'preset', preset: 'claude_code' }` to use Claude Code’s system prompt. Pass an array of strings with the exported `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` constant between the static and per-request parts to [cache the static part of a custom prompt](agent-sdk-modifying-system-prompts.md#cache-the-static-part-of-a-custom-prompt). When using the preset object form, add `append` to extend it with additional instructions, and set `excludeDynamicSections: true` to move per-session context into the first user message for [better prompt-cache reuse across machines](agent-sdk-modifying-system-prompts.md#improve-prompt-caching-across-users-and-machines). Set `snapshot: false` to rebuild the prompt on every request instead of [reusing the prompt the session recorded on its first request](agent-sdk-modifying-system-prompts.md#change-the-prompt-of-an-existing-session). To set `snapshot` on a custom prompt, pass the `{ type: 'custom', prompt }` form. The `{ type: 'custom' }` form and the `snapshot` field require TypeScript Agent SDK v0.3.257 or later |
| `taskBudget`                      | `{ total: number }`                                                                                                                                                                                        | `undefined`                                 | *Alpha.* API-side task budget in tokens. When set, the model is told its remaining token budget so it can pace tool use and wrap up before the limit                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `thinking`                        | [`ThinkingConfig`](#thinkingconfig)                                                                                                                                                                        | `{ type: 'adaptive' }` for supported models | Controls Claude’s thinking/reasoning behavior. See [`ThinkingConfig`](#thinkingconfig) for options                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `title`                           | `string`                                                                                                                                                                                                   | `undefined`                                 | Display title for the session. When resuming via `resume` or `continue`, the resumed session’s persisted title takes precedence; use [`renameSession()`](#renamesession) to retitle an existing session                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `toolAliases`                     | `Record<string, string>`                                                                                                                                                                                   | `undefined`                                 | Map built-in tool names to MCP tool names so Claude calls your MCP implementation in place of the built-in. For example, `{ Bash: 'mcp__workspace__bash' }`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `toolConfig`                      | [`ToolConfig`](#toolconfig)                                                                                                                                                                                | `undefined`                                 | Configuration for built-in tool behavior. See [`ToolConfig`](#toolconfig) for details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `tools`                           | `string[] | { type: 'preset'; preset: 'claude_code' }`                                                                                                                                                     | `undefined`                                 | Tool configuration. Pass an array of tool names or use the preset to get Claude Code’s default tools                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `verbatimPrompts`                 | `boolean`                                                                                                                                                                                                  | `false`                                     | Deliver every prompt as written. The SDK sends each user message with `client_composed: true`. See [`client_composed`](#sdkusermessage) for what Claude Code skips on those messages. Use this option when your prompt text includes content the end user didn’t type. For per-turn control, leave it off and set `client_composed` on individual streamed messages instead. Requires TypeScript Agent SDK v0.3.280 or later and Claude Code v2.1.248 or later; the Claude Code version bundled with those SDK versions satisfies the Claude Code requirement                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |


[​](#handle-slow-or-stalled-api-responses)

Handle slow or stalled API responses

The CLI subprocess reads several environment variables that control API timeouts and stall detection. Pass them through the `env` option:

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const result = query({
  prompt: "Analyze this code",
  options: {
    env: {
      ...process.env,
      API_TIMEOUT_MS: "120000",
      CLAUDE_CODE_MAX_RETRIES: "2",
      CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS: "120000",
    },
  },
});
```

- `API_TIMEOUT_MS`: per-request timeout on the Anthropic client, in milliseconds. Default `600000`. Applies to the main loop and all subagents.
- `CLAUDE_CODE_MAX_RETRIES`: maximum API retries. Default `10`, capped at `15`. Each retry gets its own `API_TIMEOUT_MS` window. For unattended runs that need to wait through longer outages, set [`CLAUDE_CODE_RETRY_WATCHDOG=1`](../02-Claude-Code-CLI/errors.md#tune-retry-behavior): it retries transient capacity errors indefinitely and, on Claude Code v2.1.199 or later, raises the default for other transient errors to `300` and removes the cap on this variable.
- `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS`: stall watchdog for subagents. While the stream watchdog is on, the default is `CLAUDE_STREAM_IDLE_TIMEOUT_MS` plus 5 minutes, which comes to `600000` unless you raise that variable. With the stream watchdog off, the default is `600000`. Before v2.1.257, the default was always `600000`. The timer resets on each stream event. On a stall, Claude Code aborts the subagent and reports the stall to the parent. For a background subagent, it also marks the task failed and attaches any partial result.
- `CLAUDE_ENABLE_STREAM_WATCHDOG` with `CLAUDE_STREAM_IDLE_TIMEOUT_MS`: stream watchdog that aborts the request when headers have arrived but the response body stops streaming. The watchdog is on by default for all providers; set `CLAUDE_ENABLE_STREAM_WATCHDOG=0` to disable it. `CLAUDE_STREAM_IDLE_TIMEOUT_MS` defaults to `300000` and is clamped to that minimum. After the abort, [Automatic retries](../02-Claude-Code-CLI/errors.md#automatic-retries) covers what Claude Code does, based on how far the response had progressed. While the watchdog waits out a response that a gateway behind `ANTHROPIC_BASE_URL` holds open with keep-alive pings, a host that sets `includePartialMessages` keeps receiving `ping` [stream events](#sdkpartialassistantmessage), so read those frames as liveness rather than timing the session out on silence. Before v2.1.257, the frames stopped 5 minutes after the last real stream event.


[​](#query-object)

`Query` object

Interface returned by the `query()` function.

```python
interface Query extends AsyncGenerator<SDKMessage, void> {
  interrupt(): Promise<SDKControlInterruptResponse | undefined>;
  rewindFiles(
    userMessageId: string,
    options?: { dryRun?: boolean }
  ): Promise<RewindFilesResult>;
  setPermissionMode(mode: PermissionMode): Promise<void>;
  setModel(model?: string): Promise<void>;
  setMaxThinkingTokens(maxThinkingTokens: number | null): Promise<void>;
  applyFlagSettings(settings: {
    [K in keyof Settings]?: K extends 'effortLevel'
      ? 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null
      : Settings[K] | null;
  }): Promise<void>;
  updateSettings(
    source: 'localSettings' | 'userSettings',
    settings: Record<string, unknown>,
  ): Promise<void>;
  initializationResult(): Promise<SDKControlInitializeResponse>;
  reinitialize(): Promise<SDKControlInitializeResponse>;
  supportedCommands(): Promise<SlashCommand[]>;
  supportedModels(): Promise<ModelInfo[]>;
  supportedAgents(): Promise<AgentInfo[]>;
  mcpServerStatus(): Promise<McpServerStatus[]>;
  getContextUsage(opts?: {
    detail?: 'summary' | 'full';
  }): Promise<SDKControlGetContextUsageResponse>;
  readFile(
    path: string,
    options?: { maxBytes?: number; encoding?: 'utf-8' | 'base64' }
  ): Promise<SDKControlReadFileResponse | null>;
  reloadPlugins(options?: {
    holdOnCacheImpact?: boolean;
  }): Promise<SDKControlReloadPluginsResponse>;
  reloadSkills(): Promise<SDKControlReloadSkillsResponse>;
  reloadOutputStyles(): Promise<SDKControlReloadOutputStylesResponse>;
  accountInfo(): Promise<AccountInfo>;
  reconnectMcpServer(serverName: string): Promise<void>;
  toggleMcpServer(serverName: string, enabled: boolean): Promise<void>;
  setMcpServers(servers: Record<string, McpServerConfig>): Promise<McpSetServersResult>;
  readMcpResource(serverName: string, uri: string): Promise<SDKControlMcpReadResourceResponse>;
  streamInput(stream: AsyncIterable<SDKUserMessage>): Promise<void>;
  stopTask(taskId: string): Promise<void>;
  close(): void;
}
```


[​](#methods)

Methods

| Method                                 | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|:---------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `interrupt()`                          | Interrupts the query. Only available in streaming input mode. When the CLI advertises the `interrupt_receipt_v1` capability in [`SDKSystemMessage.capabilities`](#sdksystemmessage), resolves with an [`SDKControlInterruptResponse`](#sdkcontrolinterruptresponse) listing the messages that were pending when the interrupt arrived. Resolves `undefined` on CLIs before v2.1.205                                                                                                                                        |
| `rewindFiles(userMessageId, options?)` | Restores files to their state at the specified user message. Pass `{ dryRun: true }` to preview changes. Requires `enableFileCheckpointing: true`. See [File checkpointing](agent-sdk-file-checkpointing.md)                                                                                                                                                                                                                                                                                                         |
| `setPermissionMode()`                  | Changes the permission mode (only available in streaming input mode)                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `setModel()`                           | Changes the model (only available in streaming input mode). Passing `undefined` or the string `"default"` resets to [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md)                                                                                                                                                                                                                                                                                                                                                   |
| `setMaxThinkingTokens()`               | *Deprecated:* Use the `thinking` option instead. Changes the maximum thinking tokens. Passing `null` resets thinking to the session default: a mid-session override is cleared, and thinking stays off for sessions that have it disabled                                                                                                                                                                                                                                                                                  |
| `applyFlagSettings(settings)`          | Merges settings into the session’s flag settings layer at runtime (only available in streaming input mode). See [`applyFlagSettings()`](#applyflagsettings)                                                                                                                                                                                                                                                                                                                                                                |
| `updateSettings(source, settings)`     | Writes one allowlisted key to the project’s local settings file or your user settings file, so the value persists for later sessions. See [`updateSettings()`](#updatesettings). Requires TypeScript SDK v0.3.257 or later, which bundles Claude Code v2.1.257                                                                                                                                                                                                                                                             |
| `initializationResult()`               | Returns the full initialization result including supported commands, models, account info, and output style configuration                                                                                                                                                                                                                                                                                                                                                                                                  |
| `reinitialize()`                       | Re-sends the `initialize` control request to the running CLI and returns a fresh result instead of the cached first-connect result. Use it after a transport gap, such as reattaching to a session after a disconnect, so pending permission requests reach your `canUseTool` callback again. Make the callback idempotent per request ID, because a request whose response was lost is dispatched again. Requires Claude Code v2.1.195 or later                                                                           |
| `supportedCommands()`                  | Returns available commands. From Agent SDK v0.3.216 the list reflects mid-session command changes; see [`SDKCommandsChangedMessage`](#sdkcommandschangedmessage)                                                                                                                                                                                                                                                                                                                                                           |
| `supportedModels()`                    | Returns available models with display info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `supportedAgents()`                    | Returns available subagents as [`AgentInfo`](#agentinfo)`[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `mcpServerStatus()`                    | Returns the status of connected MCP servers as [`McpServerStatus`](#mcpserverstatus)`[]`                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `getContextUsage(opts?)`               | Returns an [`SDKControlGetContextUsageResponse`](#sdkcontrolgetcontextusageresponse) breaking down the session’s context window usage by category, skill, and tool. With the default `detail`, it is the same data `/context` shows in an interactive session, computed with token-counting API requests that don’t appear in the message stream; see [how these requests are handled](#sdkcontrolgetcontextusageresponse). The [`detail` option](#sdkcontrolgetcontextusageresponse) requires Agent SDK v0.3.257 or later |
| `readFile(path, options?)`             | Reads a file from the session’s filesystem. Claude Code resolves the path against `cwd`; [What `readFile()` can read](#what-readfile-can-read) lists the files it serves. Pass `{ maxBytes }` to change the read cap (default 1 MB, ceiling 10 MB) and `{ encoding: 'base64' }` for binary files such as images. Resolves with an [`SDKControlReadFileResponse`](#sdkcontrolreadfileresponse), or `null` on permission denial, a missing file, or a transport error. Requires TypeScript SDK v0.2.121 or later             |
| `reloadPlugins(options?)`              | Reloads plugins from disk, so plugins you install or edit mid-session reach the running session. Resolves with an [`SDKControlReloadPluginsResponse`](#sdkcontrolreloadpluginsresponse) listing the session’s commands, subagents, plugins, and MCP server status. Requires Agent SDK v0.2.85 or later. The [`holdOnCacheImpact` option](#sdkcontrolreloadpluginsresponse) requires Agent SDK v0.3.268 or later                                                                                                            |
| `reloadSkills()`                       | Reloads skills from disk, so skills you add or edit mid-session become available to the running session. Resolves with an [`SDKControlReloadSkillsResponse`](#sdkcontrolreloadskillsresponse) listing the skills available after the reload. Requires Agent SDK v0.3.163 or later                                                                                                                                                                                                                                          |
| `reloadOutputStyles()`                 | Re-reads [output styles](../02-Claude-Code-CLI/output-styles.md) from disk, so a style file you add or edit mid-session becomes available to the running session. Resolves with an [`SDKControlReloadOutputStylesResponse`](#sdkcontrolreloadoutputstylesresponse) listing the style names available after the reload. Requires Agent SDK v0.3.261 or later                                                                                                                                                                                |
| `accountInfo()`                        | Returns account information                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `reconnectMcpServer(serverName)`       | Reconnect an MCP server by name. If the name also matches an entry in a settings file such as `.mcp.json` or `~/.claude.json`, Claude Code reconnects the server you configured through [`mcpServers`](#options) or `setMcpServers()`, not the settings-file entry. That resolution order requires Claude Code v2.1.257 or later                                                                                                                                                                                           |
| `toggleMcpServer(serverName, enabled)` | Enable or disable an MCP server by name, with the same name resolution as `reconnectMcpServer()`. Disabling a server disconnects it and removes its tools. See [`toggleMcpServer()`](#togglemcpserver) for the Claude Code version this needs for each kind of server                                                                                                                                                                                                                                                      |
| `setMcpServers(servers)`               | Replace the MCP servers this method manages: servers added through it and [in-process SDK servers](#createsdkmcpserver). Resolves with an [`McpSetServersResult`](#mcpsetserversresult) naming which servers were added and removed, and any errors; that section says which other servers stay connected                                                                                                                                                                                                                  |
| `readMcpResource(serverName, uri)`     | *Alpha.* Reads one MCP Apps `ui://` resource from a connected MCP server so your application can render a tool’s widget. Resolves with an [`SDKControlMcpReadResourceResponse`](#sdkcontrolmcpreadresourceresponse). Requires TypeScript Agent SDK v0.3.280 or later                                                                                                                                                                                                                                                       |
| `streamInput(stream)`                  | Stream input messages to the query for multi-turn conversations                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `stopTask(taskId)`                     | Stop a running background task by ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `close()`                              | Close the query and terminate the underlying process. Forcefully ends the query and cleans up all resources                                                                                                                                                                                                                                                                                                                                                                                                                |


[​](#applyflagsettings)

`applyFlagSettings()`

Changes [settings](../02-Claude-Code-CLI/settings.md) on a running session without restarting the query. Use it when a setting that has no dedicated setter needs to change mid-session, such as tightening `permissions` after the agent reads untrusted input. `setModel()` and `setPermissionMode()` are dedicated setters for those two keys; `applyFlagSettings()` is the general form that accepts any subset of the settings keys, and passing `model` here behaves the same as `setModel()`. Only some keys take effect mid-session:

- **Applied on the next turn**: `effortLevel`, `ultracode`, `permissions`, `hooks`, `skillOverrides`, `fastMode`, `agent`. Switching `agent` also applies that agent’s model override and hooks on the next turn. Its system prompt applies on the next turn, or, in a session that [reuses a recorded system prompt](agent-sdk-modifying-system-prompts.md#change-the-prompt-of-an-existing-session), once the session is compacted.
- **Applied during the current turn**: `model`. If you switch `model` while Claude is working on a turn, the response Claude is already generating finishes on the old model, and the rest of the turn, starting with the next call Claude Code makes to the model, uses the new one. Subagents keep their own model. Before v2.1.212, a mid-turn switch waited for the next turn.
- **No effect mid-session**: the system prompt options. These are resolved once at startup, so the running session keeps the original value even though the call succeeds. To change them, start a new session.

`effortLevel` accepts an [effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) name. It also accepts `"ultracode"`, which requests `xhigh` effort with [ultracode](../02-Claude-Code-CLI/workflows.md#let-claude-decide-with-ultracode) on. `applyFlagSettings()` declares `effortLevel` without that value, so in TypeScript pass `{ ultracode: true, effortLevel: "xhigh" }` for the same result, or the [`ultracode`](../02-Claude-Code-CLI/settings-reference.md#ultracode) key alone to turn ultracode on at the session’s current effort level. The `ultracode` value requires Claude Code v2.1.203 or later and is accepted only by `applyFlagSettings()`, not by the `effortLevel` key in a settings file. Before v2.1.284, the `ultracode` key alone also set the level to `xhigh`. The values are written to the flag-settings layer, merged over what the inline `settings` option of `query()` set at startup. This is the same tier the [on-page precedence section](#settings-precedence) calls programmatic options. Successive calls shallow-merge top-level keys. A second call with `{ permissions: {...} }` replaces the entire `permissions` object from the prior call rather than deep-merging into it. To clear a key you set with `applyFlagSettings()`, pass `null` for that key. Most keys then fall back first to a value that the `settings` option of `query()` set at startup, then to lower-precedence sources. A cleared `model` resets to [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md), even when a settings file sets `model`. Passing `undefined` has no effect because JSON serialization drops it. Three keys besides `model` reset session state instead of falling back:

- `effortLevel: null` returns the session to the model’s default effort level, not to the `effort` option of `query()` or an `effortLevel` from a settings file.
- `agent: null` runs the main thread with no agent, starting with the next turn, rather than restoring the `agent` option of `query()` or an `agent` from a settings file. If the cleared agent had applied its own model, the session returns to the model it resolved at startup.
- `ultracode: null` turns ultracode off, as `false` does, rather than restoring an `ultracode` value from a settings file. The session keeps its current effort level, so pass `effortLevel` in the same call to change it.

Only available in streaming input mode, the same constraint as `setModel()` and `setPermissionMode()`. The example below switches the active model mid-session, then clears the override so the model resets to [Claude Code’s default model](../02-Claude-Code-CLI/model-config.md).

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

const q = query({ prompt: messageStream });

// Override the model for the rest of the session
await q.applyFlagSettings({ model: "claude-opus-4-6" });

// Later: clear the override; the model resets to Claude Code's default
await q.applyFlagSettings({ model: null });
```

`applyFlagSettings()` is TypeScript-only. The Python SDK does not expose an equivalent method.


[​](#updatesettings)

`updateSettings()`

Writes one allowlisted key to a settings file on disk, so the value persists for later sessions that load that source. Each source accepts one key, with a string value:

- **`"localSettings"`**: accepts `outputStyle` and merges it into the project’s local settings file, `.claude/settings.local.json`. The new style takes effect on the session’s next request.
- **`"userSettings"`**: accepts `effortLevel` and saves it as the default [effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) for the session’s current model, under [`modelSettings`](../02-Claude-Code-CLI/settings-reference.md#modelsettings) in your user settings file. Passing `max` writes nothing, because `max` is session-only. The running session keeps its current effort level either way, so call [`applyFlagSettings()`](#applyflagsettings) when you also want to change that. This source requires TypeScript SDK v0.3.277 or later, which bundles Claude Code v2.1.277.

The call rejects when the request carries any other key, when the session runs over a remote transport, and when the session’s [`settingSources`](#options) exclude the source you name. Deleting a key isn’t supported.


[​](#togglemcpserver)

`toggleMcpServer()`

Disabling a server disconnects it and removes its tools from the session. For servers you added mid-session and for in-process servers, this depends on your Claude Code version:

- A stdio, SSE, or HTTP server you added mid-session with `setMcpServers()`: removing its tools requires Claude Code v2.1.285 or later.
- An in-process server you created with [`createSdkMcpServer()`](#createsdkmcpserver), whether you passed it in `mcpServers` or with `setMcpServers()`: disconnecting it and removing its tools requires Claude Code v2.1.286 or later. Disabling one also fails its tool calls that are still running, so Claude receives an error result for each of them immediately, without waiting for your handler to return.


[​](#warmquery)

`WarmQuery`

Handle returned by [`startup()`](#startup). The subprocess is already spawned and initialized, so calling `query()` on this handle writes the prompt directly to a ready process with no startup latency.

```python
interface WarmQuery extends AsyncDisposable {
  query(prompt: string | AsyncIterable<SDKUserMessage>): Query;
  close(): void;
}
```


[​](#methods-2)

Methods

| Method          | Description                                                                                                               |
|:----------------|:--------------------------------------------------------------------------------------------------------------------------|
| `query(prompt)` | Send a prompt to the pre-warmed subprocess and return a [`Query`](#query-object). Can only be called once per `WarmQuery` |
| `close()`       | Close the subprocess without sending a prompt. Use this to discard a warm query that is no longer needed                  |

`WarmQuery` implements `AsyncDisposable`, so it can be used with `await using` for automatic cleanup.


[​](#spareprocess)

`SpareProcess`

*Alpha.* Handle returned by [`prewarm()`](#prewarm): a started Claude Code process that isn’t bound to a session yet and can be claimed once. Requires TypeScript Agent SDK v0.3.282 or later.

```python
interface SpareProcess extends AsyncDisposable {
  claim(params: {
    prompt: string | AsyncIterable<SDKUserMessage>;
    options: ClaimOptions;
  }): Query;
  readonly claimed: Promise<{ cwd: string; sessionId: string; parkedMs?: number; sdkMcpSettled: boolean }>;
  readonly exited: Promise<void>;
  close(): void;
}
```


[​](#members)

Members

| Member                       | Description                                                                                                                                                                                                                                                                                                                       |
|:-----------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `claim({ prompt, options })` | Bind the spare to a session in `options.cwd` and send its first message. Returns a [`Query`](#query-object) synchronously, as `query()` does. Can only be called once                                                                                                                                                             |
| `claimed`                    | Resolves with the session’s working directory and ID once Claude Code accepts the claim. Rejects when Claude Code refuses the claim, when the process exited or was closed first, and, with a message that starts with `option_not_applied`, when the session is running without the `model` or `maxThinkingTokens` you asked for |
| `exited`                     | Settles when the process exits, claimed or not. Replace a spare that exits before you claim it                                                                                                                                                                                                                                    |
| `close()`                    | Terminate the process. Before a claim this discards the spare and rejects `claimed`                                                                                                                                                                                                                                               |

`options.cwd` is required. A claim can also set `additionalDirectories`, `model`, `permissionMode`, `maxThinkingTokens`, a flag-settings overlay in `settings`, `appendSystemPrompt`, `title`, `agents`, and per-session tokens in `env`. Claude Code can refuse a claim, for example for a folder that doesn’t exist or one whose project settings set `env`, `agent`, or `model`. After a refusal, a prompt that `claim()` already sent gets an error result whose text starts with `not_claimed`, and the returned query then throws. Wrap the query’s loop in a try block to continue past the throw. When `claimed` rejects with a message that starts with `option_not_applied`, the session is running without the `model` or `maxThinkingTokens` you asked for. After any other rejection your prompt hasn’t run, so start the session with `query()` instead.


[​](#sdkcontrolinitializeresponse)

`SDKControlInitializeResponse`

Return type of `initializationResult()`. Contains session initialization data.

```python
type SDKControlInitializeResponse = {
  commands: SlashCommand[];
  agents: AgentInfo[];
  output_style: string;
  available_output_styles: string[];
  models: ModelInfo[];
  account: AccountInfo;
  fast_mode_state?: "off" | "cooldown" | "on";
  fast_mode_disabled_reason?: FastModeDisabledReason;
  hooks_applied?: boolean;
  sdk_mcp_manifests_parked?: Record<
    string,
    | "parked"
    | "already_connected"
    | "protocol_version_mismatch"
    | "malformed"
    | "not_honoured"
  >;
};
```

`hooks_applied` reports whether Claude Code registered the `hooks` that the `initialize` request carried. The SDK sends that request once when the session starts and again on each [`reinitialize()`](#query-object) call. The field requires Agent SDK v0.3.238 or later. Claude Code omits the field when the request carried no hooks. When the request carried hooks, the value depends on whether the request is the session’s first initialize and, for a repeated one, on how it reached the session:

- `true`: Claude Code registered the hooks. A session’s first initialize returns this value. A repeated initialize sent over the CLI’s stdin also returns `true`. In that case the hooks in the new request replace the hooks registered earlier.
- `false`: Claude Code ignored the hooks. A repeated initialize sent to a remote session returns this value, so a second client that joins a session can’t replace the hooks the first client registered.

Before Agent SDK v0.3.238, the response never carried the field, and Claude Code ignored `hooks` on every repeated initialize. The request’s `sdkMcpServerManifests` field and the response’s `sdk_mcp_manifests_parked` field are for the in-process [SDK MCP servers](agent-sdk-custom-tools.md) you created with [`createSdkMcpServer()`](#createsdkmcpserver). Your application doesn’t set or read either field. The response always reports `fast_mode_state`, and when something blocks [fast mode](../02-Claude-Code-CLI/fast-mode.md), `fast_mode_disabled_reason` carries the reason code alongside it, so you can explain the blocked state instead of re-deriving availability. Both behaviors require Claude Code v2.1.219 or later. Before v2.1.219, the response omitted `fast_mode_state` when fast mode wasn’t available and never carried a reason. For the reason codes and their meanings, see [`fast_mode_disabled_reason`](#sdkresultmessage) on the result message. The control-response wrapper for a successful `initialize` also carries a `pending_permission_requests` array. The field is on the response wrapper itself, not in the `SDKControlInitializeResponse` payload above. Each entry is a complete `control_request` message with the same `{ type: "control_request", request_id, request }` shape the session streams for permission requests while running. The array lists the permission requests that this Claude Code process has issued and not yet resolved. The SDK reads the array for you and dispatches each entry to your [`canUseTool`](#canusetool) callback, the same redelivery that [`reinitialize()`](#query-object) triggers after a transport gap. Handle repeated request IDs idempotently, because an entry can repeat a request the callback already received before the connection dropped. The array is always present on a successful `initialize` response and is empty when this process has no unresolved permission request. Requires Claude Code v2.1.268 or later. Earlier versions could omit the field, so if you parse the wire protocol yourself, treat a missing field as an older CLI rather than as proof that nothing is pending.


[​](#sdkcontrolinterruptresponse)

`SDKControlInterruptResponse`

The interrupt receipt: the value [`interrupt()`](#query-object) resolves with on a CLI that advertises the `interrupt_receipt_v1` capability in [`SDKSystemMessage.capabilities`](#sdksystemmessage). Requires Claude Code v2.1.205 or later. Earlier CLIs answer the interrupt with an empty success payload, so `interrupt()` resolves to `undefined`.

```python
type SDKControlInterruptResponse = {
  still_queued: string[];
  cancelled?: string[];
};
```

`still_queued` lists the UUIDs of the user messages that were pending when the interrupt arrived: messages still in the queue, plus any messages Claude Code had already taken off the queue for the next turn. Once the session’s first turn has started, Claude Code processes the listed messages after the interrupt unless you cancel them first, and can merge several into one turn. If you interrupt before the first turn starts, Claude Code aborts that turn as soon as it starts, and the listed messages in that turn get no response. Use the receipt to decide whether to resend anything. A listed message that you don’t cancel enters the conversation whether or not it gets a response, so resending it delivers it to Claude twice. Interpret the list with these caveats:

- Only messages that were enqueued with a UUID appear. An empty array doesn’t mean nothing else will run.
- Only main-thread messages are listed. Messages addressed to a subagent are out of scope.
- The list can include UUIDs your client never sent, such as [scheduled task](../02-Claude-Code-CLI/scheduled-tasks.md) triggers. Ignore UUIDs you don’t recognize instead of treating them as an error.

A client that drives the CLI’s control protocol directly, rather than through `interrupt()`, can set `cancel_queued: true` on the `interrupt` control request. Claude Code v2.1.219 and later advertises support with the `interrupt_cancel_queued_v1` capability in [`SDKSystemMessage.capabilities`](#sdksystemmessage); older CLIs ignore the field and leave queued messages to run as usual. Such an interrupt also cancels every message that would otherwise be listed under `still_queued`: the receipt lists them under `cancelled` instead, `still_queued` is empty, and none of them run. The `cancelled` list carries the same caveats as `still_queued`. The `interrupt()` method never sends `cancel_queued`, so receipts it resolves with don’t carry `cancelled`. The receipt is a snapshot taken at the moment the interrupt is processed, and on a clean interrupt it arrives before the interrupted turn’s [`SDKResultMessage`](#sdkresultmessage). Read the receipt rather than inspecting the queue after that result: the loop starts the next queued turn immediately, so the queue you inspect after the result has already changed.


[​](#sdkcontrolgetcontextusageresponse)

`SDKControlGetContextUsageResponse`

Return type of [`getContextUsage()`](#query-object). With the default `detail`, this is the same payload Claude Code renders for the `/context` command in an interactive session, so alongside the token counts it carries display fields such as `color` and `gridRows` that Claude Code uses to draw the `/context` usage grid. The method’s optional `detail` argument chooses how Claude Code counts each category. The `detail` argument requires Agent SDK v0.3.257 or later.

- **`'full'`**: the default. Claude Code counts each category with [token-counting](../04-API-Reference/Guides/build-with-claude-token-counting.md) API requests. These requests don’t appear in the message stream, so cost tracking that reads the stream won’t see them. On the Anthropic API, token counting isn’t billed.
- **`'summary'`**: pass `{ detail: 'summary' }` to get an answer from the last response’s usage and local estimates instead. No token-count requests go out, and the per-category numbers are approximate.

When you send `/context` as a prompt instead of calling the method, Claude Code attaches an [`SDKContextUsage`](#sdkcontextusage) payload to the `context_usage` field of the assistant message that delivers the result. That field requires Agent SDK v0.3.232 or later.

```python
type SDKControlGetContextUsageResponse = {
  categories: {
    name: string;
    tokens: number;
    color: string;
    isDeferred?: boolean;
    kind: "used" | "free" | "buffer" | "deferred";
  }[];
  totalTokens: number;
  maxTokens: number;
  rawMaxTokens: number;
  percentage: number;
  gridRows: {
    color: string;
    isFilled: boolean;
    categoryName: string;
    tokens: number;
    percentage: number;
    squareFullness: number;
  }[][];
  model: string;
  memoryFiles: {
    path: string;
    type: string;
    tokens: number;
  }[];
  mcpTools: {
    name: string;
    serverName: string;
    tokens: number;
    isLoaded?: boolean;
  }[];
  deferredBuiltinTools?: {
    name: string;
    tokens: number;
    isLoaded: boolean;
  }[];
  systemTools?: {
    name: string;
    tokens: number;
  }[];
  systemPromptSections?: {
    name: string;
    tokens: number;
  }[];
  agents: {
    agentType: string;
    source: string;
    tokens: number;
  }[];
  slashCommands?: {
    totalCommands: number;
    includedCommands: number;
    tokens: number;
  };
  skills?: {
    totalSkills: number;
    includedSkills: number;
    tokens: number;
    skillFrontmatter: {
      name: string;
      source: string;
      tokens: number;
    }[];
  };
  autoCompactThreshold?: number;
  isAutoCompactEnabled: boolean;
  messageBreakdown?: {
    toolCallTokens: number;
    toolResultTokens: number;
    attachmentTokens: number;
    assistantMessageTokens: number;
    userMessageTokens: number;
    redirectedContextTokens: number;
    unattributedTokens: number;
    toolCallsByType: {
      name: string;
      callTokens: number;
      resultTokens: number;
    }[];
    attachmentsByType: {
      name: string;
      tokens: number;
    }[];
  };
  apiUsage: {
    input_tokens: number;
    output_tokens: number;
    cache_creation_input_tokens: number;
    cache_read_input_tokens: number;
  } | null;
};
```

Read token attribution from the collection fields:

- `categories` holds the per-category totals. Each entry’s `kind` classifies the row with the same values as [`SDKContextUsageCategory`](#sdkcontextusagecategory). Classify rows on it rather than on the display `name`. The field requires Agent SDK v0.3.268 or later.
- `mcpTools` and `agents` attribute tokens to individual MCP tools and subagents.
- `memoryFiles` lists each loaded memory file with its cost.
- `skills.skillFrontmatter` attributes the skill listing’s tokens to each included skill. The per-skill counts measure each skill’s listing entry as Claude Code actually sends it, which can be shorter than the skill’s full frontmatter. Compare `skills.totalSkills` with `skills.includedSkills` to see whether every discovered skill made it into the listing.

`totalTokens` is the session’s current context usage, and `maxTokens` is the window that usage is measured against. That window is the model’s context window, or the lower auto-compaction window when one applies. `rawMaxTokens` carries the same value as `maxTokens`, and `percentage` is `totalTokens` as a rounded percentage of that window. `apiUsage` holds the usage from the latest API response, not a running total for the session. Claude Code leaves the optional `deferredBuiltinTools`, `systemTools`, and `systemPromptSections` diagnostics unset, so expect them to be absent even though the type declares them.


[​](#sdkcontrolreadfileresponse)

`SDKControlReadFileResponse`

Return type of [`readFile()`](#query-object).

```python
type SDKControlReadFileResponse = {
  contents: string;
  absPath: string;
  truncated?: boolean;
  encoding?: 'base64';
};
```

`contents` holds the file text, or base64 data when you requested `encoding: 'base64'`; the response’s `encoding` field is set to `'base64'` in that case. `absPath` is the resolved absolute path. `truncated` is set when the file was longer than the `maxBytes` cap and the contents were cut at that limit.


[​](#what-readfile-can-read)

What `readFile()` can read

`readFile()` serves a narrower set of files than the Read tool:

- A regular file inside one of the session’s working directories, such as `cwd` and `additionalDirectories`
- A few of Claude Code’s own files for the session, such as tool results

`Read` deny and ask rules still block a matching path, and a broad `Read` allow rule doesn’t open the rest of the filesystem to `readFile()`. For anything else the call resolves with `null`.


[​](#sdkcontrolreloadpluginsresponse)

`SDKControlReloadPluginsResponse`

Return type of [`reloadPlugins()`](#query-object).

```python
type SDKControlReloadPluginsResponse = {
  commands: SlashCommand[];
  agents: AgentInfo[];
  plugins: {
    name: string;
    path: string;
    source?: string;
    version?: string;
  }[];
  mcpServers: McpServerStatus[];
  error_count: number;
  held?: boolean;
  cache_impact?: {
    mcp_servers_added: string[];
    mcp_servers_removed: string[];
    lsp_tool_change: ("adds" | "may-add" | "removes" | "may-remove") | null;
  };
};
```

The collection fields describe the session after the call:

- `commands`, `agents`, and `mcpServers`: the session’s commands, subagents, and MCP server status, in the same shapes that `supportedCommands()`, `supportedAgents()`, and `mcpServerStatus()` return. `supportedAgents()` keeps returning the list captured at initialization, so read `agents` here for the set after a reload
- `plugins`: each loaded plugin with its `name` and install `path`. `version` repeats what the plugin’s manifest declares and is plugin-author-controlled, so validate it before trusting it. It’s omitted when the manifest declares none
- `error_count`: the number of errors from loading plugins

Pass `{ holdOnCacheImpact: true }` to `reloadPlugins()` to hold a reload that would invalidate the conversation’s prompt cache instead of applying it. Claude Code runs the check that the interactive `/reload-plugins` command makes before it [warns about the cache cost](../02-Claude-Code-CLI/prompt-caching.md#enabling-or-disabling-a-plugin). The option requires Agent SDK v0.3.268 or later. A Claude Code executable older than v2.1.268, such as one you point `pathToClaudeCodeExecutable` at, ignores the option and applies the reload. When you pass the option, read `held` to learn what happened:

- `true`: the reload wasn’t applied, and the collection fields describe the session as it still is. `cache_impact` says what applying would change. To apply anyway, call `reloadPlugins()` again without the option.
- `false`: the check found no cache impact, and the reload was applied.
- Absent: you didn’t pass the option, or the Claude Code executable is older than v2.1.268 and applied the reload.

`cache_impact` is present only alongside `held: true`. `mcp_servers_added` and `mcp_servers_removed` name the plugin MCP servers the reload would register or drop, as scoped `plugin:<plugin>:<server>` names. The names are plugin-authored, so validate them before showing them. `lsp_tool_change` says whether applying would add or remove the LSP tool, or `null` when it would do neither. The `may-` forms mean the check couldn’t fully see the pending plugin set.


[​](#sdkcontrolreloadskillsresponse)

`SDKControlReloadSkillsResponse`

Return type of [`reloadSkills()`](#query-object).

```python
type SDKControlReloadSkillsResponse = {
  skills: SlashCommand[];
};
```

`skills` lists the skills available after the reload, in the same [`SlashCommand`](#slashcommand) shape that `supportedCommands()` returns.


[​](#sdkcontrolreloadoutputstylesresponse)

`SDKControlReloadOutputStylesResponse`

Return type of [`reloadOutputStyles()`](#query-object).

```python
type SDKControlReloadOutputStylesResponse = {
  available_output_styles: string[];
};
```

`available_output_styles` lists the names of the built-in and custom output styles available after the reload.


[​](#sdkcontrolmcpreadresourceresponse)

`SDKControlMcpReadResourceResponse`

Return type of [`readMcpResource()`](#query-object), carrying the MCP server’s `resources/read` result. Requires TypeScript Agent SDK v0.3.280 or later.

```python
type SDKControlMcpReadResourceResponse = {
  contents: {
    uri: string;
    mimeType?: string;
    text?: string;
    blob?: string;
    _meta?: Record<string, unknown>;
  }[];
};
```

Pass `readMcpResource()` the server name as `mcpServerStatus()` reports it and a `ui://` URI, such as the `ui.resourceUri` a tool declares in its [`_meta`](#mcpserverstatus). The call rejects for any other URI scheme, for an [SDK MCP server](#createsdkmcpserver) your application hosts itself, and for a server that isn’t connected. It’s available when the init message’s [`capabilities`](#sdksystemmessage) include `mcp_read_resource_v1`. Each `contents` entry is one content item as the server sent it, minus any `_meta` key under the `com.anthropic/` prefix, which is reserved for Claude Code. `blob` holds base64 data for a binary item, and `_meta` is the item’s own `_meta`, where an MCP Apps server puts the resource’s `ui.csp` and `ui.permissions`. The contents are untrusted third-party HTML, so render them in a sandbox.


[​](#agentdefinition)

`AgentDefinition`

Configuration for a subagent defined programmatically.

```python
type AgentDefinition = {
  description: string;
  tools?: string[];
  disallowedTools?: string[];
  prompt: string;
  model?: string;
  mcpServers?: AgentMcpServerSpec[];
  skills?: string[];
  initialPrompt?: string;
  maxTurns?: number;
  background?: boolean;
  omitClaudeMd?: boolean;
  memory?: "user" | "project" | "local";
  effort?: "low" | "medium" | "high" | "xhigh" | "max" | number;
  permissionMode?: PermissionMode;
  criticalSystemReminder_EXPERIMENTAL?: string;
};
```

| Field                                 | Required | Description                                                                                                                                                                                                                                                                                                          |
|:--------------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `description`                         | Yes      | Natural language description of when to use this agent                                                                                                                                                                                                                                                               |
| `tools`                               | No       | Array of allowed tool names. If omitted, inherits every [tool available to subagents](../09-Agents-Patterns/sub-agents.md#available-tools). To preload Skills into the agent’s context, use the `skills` field rather than listing `'Skill'` here                                                                                    |
| `disallowedTools`                     | No       | Array of tool names to explicitly disallow for this agent. MCP server-level patterns are also accepted: `mcp__server` or `mcp__server__*` removes every tool from that server, and `mcp__*` removes every MCP tool from any server                                                                                   |
| `prompt`                              | Yes      | The agent’s system prompt                                                                                                                                                                                                                                                                                            |
| `model`                               | No       | Model override for this agent. Accepts an alias such as `'fable'`, `'opus'`, `'sonnet'`, `'haiku'`, `'inherit'`, or a full model ID. `'inherit'` uses the main model. When you omit it, Claude Code picks the model in the [subagent model order](../09-Agents-Patterns/sub-agents.md#choose-a-model)                                |
| `mcpServers`                          | No       | MCP server specifications for this agent                                                                                                                                                                                                                                                                             |
| `skills`                              | No       | Array of skill names to preload into the agent context                                                                                                                                                                                                                                                               |
| `initialPrompt`                       | No       | Auto-submitted as the first user turn when this agent runs as the main thread agent                                                                                                                                                                                                                                  |
| `maxTurns`                            | No       | Maximum number of agentic turns (API round-trips) before stopping                                                                                                                                                                                                                                                    |
| `background`                          | No       | Run this agent as a non-blocking background task when invoked                                                                                                                                                                                                                                                        |
| `omitClaudeMd`                        | No       | Run this agent without the user, project, and local CLAUDE.md files when it runs as a subagent; managed policy files still load. Use it for agents that take everything they need from the Agent tool prompt. Ignored when this agent runs as the main thread agent. Requires TypeScript Agent SDK v0.3.271 or later |
| `memory`                              | No       | Memory source for this agent: `'user'`, `'project'`, or `'local'`                                                                                                                                                                                                                                                    |
| `effort`                              | No       | Reasoning effort level for this agent. Accepts a named level or an integer                                                                                                                                                                                                                                           |
| `permissionMode`                      | No       | Permission mode for tool execution within this agent. The [subagent inheritance rules](agent-sdk-permissions.md#available-modes) decide when it applies. See [`PermissionMode`](#permissionmode)                                                                                                               |
| `criticalSystemReminder_EXPERIMENTAL` | No       | Experimental: Critical reminder added to the system prompt                                                                                                                                                                                                                                                           |


[​](#agentmcpserverspec)

`AgentMcpServerSpec`

Specifies MCP servers available to a subagent. Can be a server name (string referencing a server from the parent’s `mcpServers` config) or an inline server configuration record mapping server names to configs.

```python
type AgentMcpServerSpec = string | Record<string, McpServerConfigForProcessTransport>;
```

Where `McpServerConfigForProcessTransport` is `McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig`.


[​](#settingsource)

`SettingSource`

Controls which filesystem-based configuration sources the SDK loads settings from.

```python
type SettingSource = "user" | "project" | "local";
```

| Value       | Description                                                               | Location                      |
|:------------|:--------------------------------------------------------------------------|:------------------------------|
| `'user'`    | Global user settings                                                      | `~/.claude/settings.json`     |
| `'project'` | Shared project settings (version controlled)                              | `.claude/settings.json`       |
| `'local'`   | Local project settings, gitignored when Claude Code saves a setting to it | `.claude/settings.local.json` |


[​](#default-behavior)

Default behavior

When `settingSources` is omitted or `undefined`, `query()` loads the same filesystem settings as the Claude Code CLI: user, project, and local. See [What settingSources does not control](agent-sdk-claude-code-features.md#what-settingsources-does-not-control) for inputs that are read regardless of this option, and how to disable them.


[​](#why-use-settingsources)

Why use settingSources

**Disable filesystem settings:**

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Do not load user, project, or local settings from disk
const result = query({
  prompt: "Analyze this code",
  options: { settingSources: [] }
});
```

**Load only specific setting sources:**

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

// Load only project settings, ignore user and local
const result = query({
  prompt: "Run CI checks",
  options: {
    settingSources: ["project"] // Only .claude/settings.json
  }
});
```

To load CLAUDE.md project instructions, include `"project"` in `settingSources`. See [Modify system prompts](agent-sdk-modifying-system-prompts.md#claude-md-files-for-project-level-instructions) for how CLAUDE.md loading interacts with the system prompt options.


[​](#settings-precedence)

Settings precedence

When multiple sources are loaded, settings are merged with this precedence (highest to lowest):

1.  Local settings (`.claude/settings.local.json`)
2.  Project settings (`.claude/settings.json`)
3.  User settings (`~/.claude/settings.json`)

Programmatic options such as `agents`, `allowedTools`, and `settings` override user, project, and local filesystem settings. Managed policy settings take precedence over programmatic options.


[​](#permissionmode)

`PermissionMode`

```python
type PermissionMode =
  | "default" // Standard permission behavior
  | "acceptEdits" // Auto-accept file edits
  | "bypassPermissions" // Bypass permission checks; explicit ask rules still prompt
  | "plan" // Planning mode - explore without editing
  | "dontAsk" // Don't prompt for permissions, deny if not pre-approved
  | "auto"; // A model classifier reviews actions such as shell commands and network requests
```


[​](#canusetool)

`CanUseTool`

Custom permission function type for controlling tool usage. The function is the SDK replacement for the interactive permission prompt: it’s invoked only when the [permission evaluation flow](agent-sdk-permissions.md#how-permissions-are-evaluated) resolves to a prompt. Tool calls already approved by an `allowedTools` entry, a settings allow rule, or the permission mode, such as `acceptEdits` or `bypassPermissions`, never invoke it. To gate every tool call, use a [`PreToolUse` hook](agent-sdk-hooks.md) instead. An allow rule doesn’t pre-approve the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves); see [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) for which of them reach the callback and what happens in `dontAsk` and `auto` mode.

```python
type CanUseTool = (
  toolName: string,
  input: Record<string, unknown>,
  options: {
    signal: AbortSignal;
    suggestions?: PermissionUpdate[];
    blockedPath?: string;
    mcpServer?: { name: string; source: string };
    decisionReason?: string;
    defaultToNo?: boolean;
    suppressAlwaysAllowRule?: boolean;
    toolUseID: string;
    agentID?: string;
    requestId: string;
  }
) => Promise<PermissionResult | null>;
```

| Option                    | Type                                        | Description                                                                                                                                                                                                                                                                                                  |
|:--------------------------|:--------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `signal`                  | `AbortSignal`                               | Signaled if the operation should be aborted                                                                                                                                                                                                                                                                  |
| `suggestions`             | [`PermissionUpdate`](#permissionupdate)`[]` | Suggested permission updates so the user is not prompted again for this tool. Bash prompts include a suggestion with the `localSettings` [destination](#permissionupdatedestination), so returning it in `updatedPermissions` writes the rule to `.claude/settings.local.json` and persists across sessions. |
| `blockedPath`             | `string`                                    | The file path that triggered the permission request, if applicable                                                                                                                                                                                                                                           |
| `mcpServer`               | `{ name: string; source: string }`          | For an `mcp__*` tool, the MCP server that serves it and where that server’s definition came from, with the fields of [`McpServerProvenance`](#mcpserverprovenance). Absent for other tools. Requires Agent SDK v0.3.274 or later                                                                             |
| `decisionReason`          | `string`                                    | Explains why this permission request was triggered                                                                                                                                                                                                                                                           |
| `defaultToNo`             | `boolean`                                   | When `true`, a single stray keystroke must not approve this request: open your prompt on its decline option, don’t pre-select approve, and offer no one-key approve shortcut. Requires Agent SDK v0.3.268 or later                                                                                           |
| `suppressAlwaysAllowRule` | `boolean`                                   | When `true`, don’t offer a persistent always-allow choice for this request. Requires Agent SDK v0.3.268 or later                                                                                                                                                                                             |
| `toolUseID`               | `string`                                    | Unique identifier for this specific tool call within the assistant message                                                                                                                                                                                                                                   |
| `agentID`                 | `string`                                    | If running within a sub-agent, the sub-agent’s ID                                                                                                                                                                                                                                                            |
| `requestId`               | `string`                                    | The `control_request` envelope’s `request_id`. A `control_response` your application sends outside the SDK, such as a signed HTTP POST, must echo this value so the Claude Code process can match the reply to the request                                                                                   |

The callback normally resolves the request by returning a [`PermissionResult`](#permissionresult), which the SDK writes back over its transport as the `control_response`. Return `null` only when your application has already sent the `control_response` for this request over its own channel, echoing `requestId`; the SDK then skips writing the response to its transport. Returning `null` in any other case leaves the tool call blocked indefinitely, because no `control_response` is ever sent and permission prompts don’t time out. The `requestId` option and the `null` return value require Claude Code v2.1.199 or later.


[​](#permissionresult)

`PermissionResult`

Result of a permission check.

```python
type PermissionResult =
  | {
      behavior: "allow";
      updatedInput?: Record<string, unknown>;
      updatedPermissions?: PermissionUpdate[];
      toolUseID?: string;
    }
  | {
      behavior: "deny";
      message: string;
      interrupt?: boolean;
      toolUseID?: string;
    };
```


[​](#toolconfig)

`ToolConfig`

Configuration for built-in tool behavior.

```python
type ToolConfig = {
  askUserQuestion?: {
    previewFormat?: "markdown" | "html";
  };
};
```

| Field                           | Type                  | Description                                                                                                                                                                        |
|:--------------------------------|:----------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `askUserQuestion.previewFormat` | `'markdown' | 'html'` | Opts into the `preview` field on [`AskUserQuestion`](agent-sdk-user-input.md#question-format) options and sets its content format. When unset, Claude does not emit previews |


[​](#mcpserverconfig)

`McpServerConfig`

Configuration for MCP servers.

```python
type McpServerConfig =
  | McpStdioServerConfig
  | McpSSEServerConfig
  | McpHttpServerConfig
  | McpSdkServerConfigWithInstance;
```


[​](#mcpstdioserverconfig)

`McpStdioServerConfig`

```python
type McpStdioServerConfig = {
  type?: "stdio";
  command: string;
  args?: string[];
  env?: Record<string, string>;
};
```


[​](#mcpsseserverconfig)

`McpSSEServerConfig`

```python
type McpSSEServerConfig = {
  type: "sse";
  url: string;
  headers?: Record<string, string>;
};
```


[​](#mcphttpserverconfig)

`McpHttpServerConfig`

```python
type McpHttpServerConfig = {
  type: "http";
  url: string;
  headers?: Record<string, string>;
};
```


[​](#mcpsdkserverconfigwithinstance)

`McpSdkServerConfigWithInstance`

```python
type McpSdkServerConfigWithInstance = {
  type: "sdk";
  name: string;
  timeout?: number;
  instance: McpServer;
};
```


[​](#mcpclaudeaiproxyserverconfig)

`McpClaudeAIProxyServerConfig`

```python
type McpClaudeAIProxyServerConfig = {
  type: "claudeai-proxy";
  url: string;
  id: string;
};
```


[​](#sdkpluginconfig)

`SdkPluginConfig`

Configuration for loading plugins in the SDK.

```python
type SdkPluginConfig = {
  type: "local";
  path: string;
  skipMcpDiscovery?: boolean;
};
```

| Field              | Type      | Description                                                                                                                                                                                                   |
|:-------------------|:----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type`             | `'local'` | Must be `'local'` (only local plugins currently supported)                                                                                                                                                    |
| `path`             | `string`  | Absolute or relative path to the plugin directory                                                                                                                                                             |
| `skipMcpDiscovery` | `boolean` | When `true`, the SDK loads skills, hooks, agents, and commands from this plugin but does not read its `.mcp.json` or manifest `mcpServers`. Set this when your application owns the plugin’s MCP connections. |

**Example:**

```python
plugins: [
  { type: "local", path: "./my-plugin" },
  { type: "local", path: "/absolute/path/to/plugin" }
];
```

For complete information on creating and using plugins, see [Plugins](agent-sdk-plugins.md).


[​](#message-types)

Message Types


[​](#sdkmessage)

`SDKMessage`

Union type of all possible messages returned by the query.

```python
type SDKMessage =
  | SDKAssistantMessage
  | SDKUserMessage
  | SDKUserMessageReplay
  | SDKResultMessage
  | SDKSystemMessage
  | SDKPartialAssistantMessage
  | SDKCompactBoundaryMessage
  | SDKStatusMessage
  | SDKLocalCommandOutputMessage
  | SDKHookStartedMessage
  | SDKHookProgressMessage
  | SDKHookResponseMessage
  | SDKPluginInstallMessage
  | SDKToolProgressMessage
  | SDKAuthStatusMessage
  | SDKTaskNotificationMessage
  | SDKTaskStartedMessage
  | SDKTaskProgressMessage
  | SDKTaskUpdatedMessage
  | SDKBackgroundTasksChangedMessage
  | SDKThinkingTokensMessage
  | SDKSessionStateChangedMessage
  | SDKWorkerShuttingDownMessage
  | SDKCommandsChangedMessage
  | SDKNotificationMessage
  | SDKFilesPersistedEvent
  | SDKToolUseSummaryMessage
  | SDKMemoryRecallMessage
  | SDKRateLimitEvent
  | SDKElicitationCompleteMessage
  | SDKPermissionDeniedMessage
  | SDKPromptSuggestionMessage
  | SDKAPIRetryMessage
  | SDKMirrorErrorMessage
  | SDKInformationalMessage
  | SDKConversationResetMessage;
```


[​](#sdkassistantmessage)

`SDKAssistantMessage`

Assistant response message.

```python
type SDKAssistantMessage = {
  type: "assistant";
  uuid: UUID;
  session_id: string;
  message: BetaMessage; // From Anthropic SDK
  parent_tool_use_id: string | null;
  error?: SDKAssistantMessageError;
  aborted?: true;
  agent_id?: string;
  timestamp?: string;
  context_usage?: SDKContextUsage;
  usage_report?: SDKUsageReport;
  user_message_uuid?: string;
  user_message_uuids?: string[];
  resume_reason?: string;
};
```

The `message` field is a [`BetaMessage`](../04-API-Reference/Endpoints/messages-create.md) from the Anthropic SDK. It includes fields like `id`, `content`, `model`, `stop_reason`, and `usage`. `SDKAssistantMessageError` is one of: `'authentication_failed'`, `'oauth_org_not_allowed'`, `'account_on_hold'`, `'billing_error'`, `'rate_limit'`, `'overloaded'`, `'invalid_request'`, `'model_not_found'`, `'server_error'`, `'max_output_tokens'`, `'cloud_credential_error'`, or `'unknown'`. Four of these values mean more than their names say:

- `'model_not_found'`: the selected model doesn’t exist or isn’t available to your account or deployment
- `'overloaded'`: the API returned a 529 because the server is at capacity, as opposed to `'rate_limit'`, which is a 429 against your quota
- `'account_on_hold'`: [your account is on hold](../02-Claude-Code-CLI/errors.md#your-account-is-on-hold)
- `'cloud_credential_error'`: Claude Code couldn’t obtain usable AWS or Google Cloud credentials on the machine it runs on, so no request reached the cloud provider. The usual cause is a cloud sign-in that expired or was never completed on that machine, though a briefly unreachable credential service reports the same value. See [Could not load AWS or Google Cloud credentials](../02-Claude-Code-CLI/errors.md#could-not-load-aws-or-google-cloud-credentials). Requires TypeScript Agent SDK v0.3.267 or later, which bundles Claude Code v2.1.267

`aborted` is `true` when an interrupt or abort truncated the assistant message before the stream completed: the message has no `stop_reason` and the content may end mid-word. The field is absent on normally completed messages. It requires Agent SDK v0.3.214 or later. `agent_id` identifies the subagent that produced the message and is absent on main-thread messages. The value equals the `task_id` on that subagent’s [`task_started`](#sdktaskstartedmessage) and other task events, and is unchanged when the subagent is [resumed](agent-sdk-subagents.md#resume-subagents). The field requires Agent SDK v0.3.292 or later. Match a subagent’s messages to its task events on `agent_id` rather than pairing a message’s `parent_tool_use_id` with a task event’s `tool_use_id`. When a tool call resumes the subagent, the task events carry that call’s `tool_use_id`, while the messages keep the `parent_tool_use_id` of the tool call that first started the subagent, so the two no longer match. Claude Code sets `user_message_uuid` and `user_message_uuids` on the turn’s first assistant message, under the conditions in [`user_message_uuid`](#user_message_uuid). When the turn continues one that a restart interrupted, the assistant messages that carry those fields also carry [`resume_reason`](#resume_reason). `timestamp` is the ISO 8601 time when the message’s content finished generating on the process that produced it. The value comes from that machine’s clock, so use it for display only and don’t order messages by it. One API turn can produce several assistant messages that share a `message.id`, each with its own `timestamp`. When the field is absent, fall back to the time you received the message. `context_usage` is a structured copy of the `/context` report, typed as [`SDKContextUsage`](#sdkcontextusage), and requires Agent SDK v0.3.232 or later. When you send `/context` as a prompt, Claude Code delivers the report as an assistant message whose `message.content` holds the markdown table, and attaches `context_usage` to that same message. Claude Code doesn’t set the field on any other assistant message, and earlier versions deliver the `/context` table without it, so read the breakdown from the field when it’s present and fall back to the markdown text when it isn’t. `usage_report` is a structured copy of the `/usage` report, typed as [`SDKUsageReport`](#sdkusagereport), and requires Agent SDK v0.3.273 or later. When you send `/usage` as a prompt, Claude Code delivers the report as an assistant message whose `message.content` holds the text. It attaches `usage_report` to that same message only when the session meets all of these conditions:

- The session authenticates with a claude.ai credential
- The credential shows a known plan type or carries the `user:profile` scope
- The account isn’t on usage-based billing

A `claude setup-token` token passed as `CLAUDE_CODE_OAUTH_TOKEN` doesn’t qualify by default, because it carries only the `user:inference` scope. Other sessions, such as API-key sessions, deliver the text without the field, and so do earlier versions. Read the report from the field when it’s present and fall back to the text when it isn’t.


[​](#sdkusermessage)

`SDKUserMessage`

User input message.

```python
type SDKUserMessage = {
  type: "user";
  uuid?: UUID;
  session_id?: string;
  agent_id?: string;
  message: MessageParam; // From Anthropic SDK
  pasted_content?: MessageParam["content"][];
  parent_tool_use_id: string | null;
  isSynthetic?: boolean;
  shouldQuery?: boolean;
  client_composed?: true;
  tool_use_result?: unknown;
  priority?: "now" | "next" | "later";
  origin?: SDKMessageOrigin;
  inline_pastes?: string[];
};
```

Set `pasted_content` to send content the user pasted into your prompt UI rather than typed, one entry per paste, each a string or an array of content blocks. Claude Code appends each entry’s text after the typed text, in order, and may wrap each paste in `<pasted_content>` tags. Blocks other than text are ignored, so send images and documents in `message.content`. Requires Agent SDK v0.3.277 or later. Set `inline_pastes` to tell Claude Code which parts of `message.content` the user pasted rather than typed, one string per paste. The prompt text stays where the user put it. Claude Code may wrap each listed paste in `<pasted_content>` tags where it stands, so Claude can tell pasted material from the user’s own words. Only pastes in the prompt’s last text block are wrapped. Requires TypeScript Agent SDK v0.3.280 or later. Each paste field has a size limit:

- `pasted_content`: if the entries plus the content blocks inside them number more than 1,000, Claude Code ignores the whole field.
- `inline_pastes`: Claude Code uses the first 100 entries that aren’t blank and ignores the rest.

Set `shouldQuery`, `client_composed`, or `priority` to change how Claude Code handles a message you send:

- `shouldQuery`: set it to `false` to append the message to the transcript without triggering an assistant turn. The message is held and merged into the next user message that does trigger a turn. Use this to inject context, such as the output of a command you ran out of band, without spending a model call on it.
- `client_composed`: set it to `true` to have Claude Code deliver the message text as written. Claude Code then doesn’t expand `@path` or [`@server:resource`](../06-MCP-Tools/General/mcp.md#use-mcp-resources) mentions, and doesn’t run text that starts with `/` as a command. While the [`verbatimPrompts`](#options) option is on, the SDK sets the field on every message. Requires TypeScript Agent SDK v0.3.280 or later and Claude Code v2.1.248 or later.
- `priority`: controls when a message you send during a running turn reaches Claude:
  - `'next'`, or no `priority` field: Claude reads the message in the same turn, as soon as the tool calls it is running finish. If the turn ends first, the message starts the next turn.
  - `'later'`: Claude Code holds the message until the turn ends and sends it as a new turn.
  - `'now'` with [`origin: { kind: "human" }`](#sdkmessageorigin): on Claude Code v2.1.286 or later, work that can continue in the background moves there, and Claude reads the message in the same turn. Work that can move includes shell commands, subagents, and MCP tool calls. On v2.1.287 or later it also includes WebFetch and WebSearch calls. When Claude is only writing a response, or the work it is running can’t move, Claude Code interrupts the turn instead and Claude reads the message next.
  - `'now'` without that origin: Claude Code interrupts the turn and Claude reads the message next.

This message, sent while a turn is running, asks Claude to change course without losing a shell command that is still running:

```python
const message: SDKUserMessage = {
  type: "user",
  message: { role: "user", content: "Skip the integration tests and summarize what you have so far" },
  parent_tool_use_id: null,
  priority: "now",
  origin: { kind: "human" },
};
```

A user message that a subagent produces, such as the `tool_result` for one of its own tool calls, carries `agent_id`. See [`SDKAssistantMessage`](#sdkassistantmessage), which defines the field and its version requirement. On a message that carries a `tool_result` block, `tool_use_result` is the tool’s structured output object rather than the text sent to the model. Its shape depends on the tool named by the matching `tool_use` block, so the field is typed `unknown`; the built-in shapes are listed under [Tool Output Types](#tool-output-types). These results need handling beyond their listed shape:

- The `Agent` tool: `tool_use_result` is [`AgentOutput`](#agent-2). Render from it rather than parsing the `tool_result` text. A `completed` result’s `content` holds the subagent’s report, or, for a subagent whose report goes through a `SubagentHandback` tool call, a short note about that hand-back in place of the report. In [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) on Claude Code v2.1.271 or later, every subagent that produces a `completed` result reports that way unless it is a [fork](../09-Agents-Patterns/sub-agents.md#fork-the-current-conversation), and Claude receives the report as a separate message from the subagent.
- A WebFetch or WebSearch call that Claude Code moved to the background to deliver a `'now'` message: the user message carrying that call’s `tool_result` has `tool_use_result` set to `{ detachedToolCall: true }`. The call is still running, and Claude receives its result once it finishes. No second `tool_result` for that `tool_use_id` follows, so if your application draws a row for each tool call, mark this row as moved to the background when this message arrives. Requires Claude Code v2.1.287 or later.
- An MCP tool whose result contains `resource_link` blocks: `tool_use_result` is an object with a `resourceLinks` array of [`SDKMcpResourceLink`](#sdkmcpresourcelink) entries. Claude receives each link as a line of text in the `tool_result` block, so read `resourceLinks` to render the files the server returned instead of parsing that text. Claude Code omits `resourceLinks` when the result has no links and on results from subagents, keeps at most 50 links per result, and stops adding links once the array reaches 64 KiB of serialized JSON. `resourceLinks` requires Agent SDK v0.3.257 or later.
- An MCP tool that returns [`structuredContent`](#calltoolresult): `tool_use_result` is an object whose `structuredContent` member holds what the server sent and whose `content` member holds the [`McpOutput`](#mcpoutput) value. Results from subagents don’t carry `structuredContent`.
- An MCP tool whose `structuredContent` serializes to more than 1,048,576 characters of JSON: Claude Code leaves `structuredContent` off `tool_use_result` and sets `structuredContentOmitted: true` in its place, so your application can tell a dropped object from a tool that sent none. The other members, such as `content` and `resourceLinks`, stay, and what Claude receives doesn’t change. Tools from [in-process SDK servers](agent-sdk-custom-tools.md) and tools whose `tools/list` entry declares an [MCP Apps `_meta.ui` resource](#mcpserverstatus) are exempt and deliver the object whole. Claude Code v2.1.287 or later applies this cap.


[​](#sdkusermessagereplay)

`SDKUserMessageReplay`

Replayed user message with required UUID.

```python
type SDKUserMessageReplay = {
  type: "user";
  uuid: UUID;
  session_id: string;
  message: MessageParam;
  parent_tool_use_id: string | null;
  isSynthetic?: boolean;
  client_composed?: true;
  tool_use_result?: unknown;
  origin?: SDKMessageOrigin;
  isReplay: true;
};
```

A user turn injected from outside the session, one whose [`origin`](#sdkmessageorigin) kind is `peer` or `channel`, reaches the stream as a replay whether it was delivered during an active turn or started a new turn while the session was idle. Before v2.1.207, an injected turn delivered while the session was idle produced no message on the stream and only appeared when you re-read the transcript.


[​](#sdkresultmessage)

`SDKResultMessage`

Final result message.

```python
type SDKResultMessage =
  | {
      type: "result";
      subtype: "success";
      uuid: UUID;
      session_id: string;
      duration_ms: number;
      duration_api_ms: number;
      is_error: boolean;
      api_error_status?: number | null;
      num_turns: number;
      result: string;
      stop_reason: string | null;
      ttft_ms?: number;
      ttft_stream_ms?: number;
      user_message_uuid?: string;
      user_message_uuids?: string[];
      resume_reason?: string;
      local_command?: string;
      request_sent_wall_ms?: number;
      first_content_frame_ms?: number;
      first_stream_post_ms?: number;
      first_stream_post_ack_ms?: number;
      first_stream_post_queue_wait_ms?: number;
      first_stream_post_queued_behind?: "durable_post" | "ephemeral_post" | "retry_backoff" | "hold" | "none";
      first_stream_post_wall_ms?: number;
      first_text_post_ms?: number;
      first_text_post_queue_wait_ms?: number;
      first_text_post_queued_behind?: "durable_post" | "ephemeral_post" | "retry_backoff" | "hold" | "none";
      first_text_post_wall_ms?: number;
      total_cost_usd: number;
      usage: NonNullableUsage;
      modelUsage: { [modelName: string]: ModelUsage };
      permission_denials: SDKPermissionDenial[];
      queued_turn_count?: number;
      structured_output?: unknown;
      deferred_tool_use?: { id: string; name: string; input: Record<string, unknown> };
      terminal_reason?: TerminalReason;
      result_index?: number;
      fast_mode_state?: FastModeState;
      fast_mode_disabled_reason?: FastModeDisabledReason;
      origin?: SDKMessageOrigin;
    }
  | {
      type: "result";
      subtype:
        | "error_max_turns"
        | "error_during_execution"
        | "error_max_budget_usd"
        | "error_max_structured_output_retries";
      uuid: UUID;
      session_id: string;
      duration_ms: number;
      duration_api_ms: number;
      is_error: boolean;
      num_turns: number;
      stop_reason: string | null;
      total_cost_usd: number;
      usage: NonNullableUsage;
      modelUsage: { [modelName: string]: ModelUsage };
      permission_denials: SDKPermissionDenial[];
      queued_turn_count?: number;
      errors: string[];
      startup_failure_reason?: SDKStartupFailureReason;
      user_message_uuid?: string;
      user_message_uuids?: string[];
      resume_reason?: string;
      terminal_reason?: TerminalReason;
      result_index?: number;
      fast_mode_state?: FastModeState;
      fast_mode_disabled_reason?: FastModeDisabledReason;
      origin?: SDKMessageOrigin;
    };
```

Several fields on the result carry diagnostic detail beyond `subtype`:

- `api_error_status`: the HTTP status code of the API error that terminated the conversation. Absent or `null` when the turn ended without an API error.
- `ttft_ms`: time to first token in milliseconds, measured when the first complete assistant message arrives. Present on the success arm only.
- `ttft_stream_ms`: time in milliseconds until the first `message_start` stream event, when the response stream opens. Lower than `ttft_ms`; the gap between the two is time spent streaming the first message. Present on the success arm only.
- `user_message_uuid`: the `uuid` of the message you sent that this turn answered. See [`user_message_uuid`](#user_message_uuid) for which results carry it.
- `user_message_uuids`: the `uuid`s of every message you sent that Claude Code answered in this turn. See [`user_message_uuids`](#user_message_uuids).
- `resume_reason`: why this turn continues one that a restart interrupted. Present on both arms. See [`resume_reason`](#resume_reason).
- `local_command`: the name of the command the turn dispatched, on the success result of a turn that a command completed without entering the agent loop, such as `/compact`. The name is folded to lowercase letters and underscores, so `/reload-plugins` reports `reload_plugins`. A command that an MCP server provides, and the built-in `/mcp`, report `mcp`. A command you defined yourself reports `custom`. The arguments are never included. Absent on every turn that entered the agent loop and on sends that ran no command. Requires Agent SDK v0.3.268 or later.
- `request_sent_wall_ms`: epoch milliseconds at which Claude Code dispatched the API request, for joins against server-side timestamps. Present only together with [`user_message_uuid`](#user_message_uuid), on a success result with `is_error` false whose turn sent an API request.
- `first_content_frame_ms`: time in milliseconds until the first `content_block_start` or `content_block_delta` stream event, counting thinking blocks as content. Present on the success arm only, when `is_error` is false. Requires Agent SDK v0.3.260 or later.
- `first_stream_post_ms`, `first_stream_post_ack_ms`, `first_stream_post_wall_ms`: timings for uploading the turn’s first stream event. Claude Code records them only in sessions it streams to claude.ai, such as [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md), and the results `query()` yields don’t carry them. Requires Agent SDK v0.3.260 or later.
- `usage`: main agent loop only. Excludes subagent and auxiliary model calls, and is per-turn in streaming-input sessions. Prefer `modelUsage` for token/cost accounting.
- `modelUsage`: per-model totals for every model call made through the query pipeline during this `query()` call, including the main loop, subagents, and internal calls such as compaction and Workflow agents. Helper calls outside that pipeline, such as the permission classifier and token-counting requests, are excluded. A call that resumes a session also counts the [per-model totals restored from the session’s earlier calls](agent-sdk-cost-tracking.md#accumulate-costs-across-multiple-calls). In streaming-input sessions the totals are cumulative across turns, so read the latest result rather than summing across results. See [Track costs in streaming input mode](agent-sdk-cost-tracking.md#track-costs-in-streaming-input-mode) for resets and [Recover totals after a session crash](agent-sdk-cost-tracking.md#recover-totals-after-a-session-crash) for zeroed results.
- `total_cost_usd`: cumulative estimated cost in USD, covering the same calls as `modelUsage` and reset at the same points. A call that resumes a session also counts the [totals restored from the session’s earlier calls](agent-sdk-cost-tracking.md#accumulate-costs-across-multiple-calls). It is an estimate, not a billing statement. See [Track cost and usage](agent-sdk-cost-tracking.md) for accuracy caveats.
- `queued_turn_count`: the number of messages you sent with `origin: { kind: "human" }` that are still waiting when Claude Code produced the result. See [`queued_turn_count`](#queued_turn_count) for what `0` and an absent field tell you.
- `result_index`: where this result falls in the run’s delivery order, counting from 0 across every result the process writes. Present on both arms. A result whose write fails still consumes its number, so a gap in the sequence means a result was lost. Requires Agent SDK v0.3.268 or later.
- `startup_failure_reason`: why Claude Code refused to start, on the `error_during_execution` result it writes before exiting on a known startup failure. See [`startup_failure_reason`](#startup_failure_reason) for the values and which failures carry it. Requires Agent SDK v0.3.274 or later.
- `terminal_reason`: why the loop ended. One of `"completed"`, `"max_turns"`, `"tool_deferred"`, `"aborted_streaming"`, `"aborted_tools"`, `"hook_stopped"`, `"stop_hook_prevented"`, `"background_requested"`, `"blocking_limit"`, `"rapid_refill_breaker"`, `"prompt_too_long"`, `"image_error"`, `"model_error"`, `"api_error"`, `"malformed_tool_use_exhausted"`, `"budget_exhausted"`, `"structured_output_retry_exhausted"`, `"tool_deferred_unavailable"`, or `"turn_setup_failed"`.
- `fast_mode_state`: one of `"on"`, `"off"`, or `"cooldown"`.
- `fast_mode_disabled_reason`: why [fast mode](../02-Claude-Code-CLI/fast-mode.md) isn’t available right now. Absent when nothing blocks fast mode, though a request may still run at standard speed. During the cooldown after a fast mode rate limit, Claude Code reports `fast_mode_state: "cooldown"` with no reason code and re-enables fast mode when the cooldown expires. Requires Claude Code v2.1.219 or later.

Use the reason code to explain why fast mode is off in your own UI instead of re-deriving availability. Each code names the check that blocked fast mode:

| Reason code            | Meaning                                                                                                                                                     |
|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `free`                 | The account doesn’t have the paid subscription or usage credits fast mode requires                                                                          |
| `preference`           | The organization has disabled fast mode                                                                                                                     |
| `extra_usage_disabled` | Usage credits are turned off for the account                                                                                                                |
| `network_error`        | The [availability check](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways) couldn’t reach `api.anthropic.com`                               |
| `unknown`              | Claude Code couldn’t determine availability                                                                                                                 |
| `not_first_party`      | The session uses a provider other than the Anthropic API                                                                                                    |
| `disabled_by_env`      | [`CLAUDE_CODE_DISABLE_FAST_MODE`](../02-Claude-Code-CLI/env-vars.md) is set                                                                                                 |
| `model_not_allowed`    | The fast mode Opus model isn’t in the organization’s [`availableModels`](../02-Claude-Code-CLI/model-config.md#restrict-model-selection) allowlist                          |
| `sdk_opt_in_required`  | The session hasn’t opted in to fast mode: pass `fastMode: true` in the [`settings`](#options) option or through [`applyFlagSettings()`](#applyflagsettings) |
| `pending`              | The availability check hasn’t completed yet                                                                                                                 |

The same pair of fields appears on [`SDKSystemMessage`](#sdksystemmessage) and on the [`SDKControlInitializeResponse`](#sdkcontrolinitializeresponse), so you can read the fast mode state before the first turn. The `origin` field forwards the [`SDKMessageOrigin`](#sdkmessageorigin) of the user message that triggered this result. When the SDK injects a synthetic follow-up turn, such as for a finished background task, the resulting `SDKResultMessage` carries `origin: { kind: "task-notification" }`. Routines whose trigger fired and server-verified messages from your other sessions arrive with this kind too, each with the `subkind` described in [Task-notification subkinds](#task-notification-subkinds). Check `kind` to distinguish results that answer your prompt from injected follow-ups before routing or suppressing them. If your application [declares scheduled runs](#declare-a-scheduled-run), their results carry `kind: "task-notification"` too, so don’t suppress on `kind` alone. When several background-task completions are queued together, Claude Code can answer them in one turn rather than one turn each. Each completion still produces its own result with this origin. All but the last of the completions Claude Code answers together produce empty results with `num_turns: 0`, in order, and the last one’s result carries the turn that answers them all. The field is absent for results emitted before any user turn, such as startup errors. When a `PreToolUse` hook returns `permissionDecision: "defer"`, the result has `stop_reason: "tool_deferred"` and `deferred_tool_use` carries the pending tool’s `id`, `name`, and `input`. Read this field to surface the request in your own UI, then resume with the same `session_id` to continue. See [Defer a tool call for later](../07-Hooks/hooks.md#defer-a-tool-call-for-later) for the full round trip.


[​](#user_message_uuid)

`user_message_uuid`

The `uuid` of the [`SDKUserMessage`](#sdkusermessage) the turn is answering, echoed so you can match Claude Code’s reply to the message you sent. Claude Code echoes a `uuid` only if you set one on the message. The field is optional on `SDKUserMessage`, and a string prompt passed to `query()` carries none. Which of your messages a turn answers depends on how the turn started:

- **A regular message you sent**, meaning one without `isSynthetic: true`: the turn answers that message for its whole run. When you send several messages close together, Claude Code can merge them into one turn, and the field then carries only the last message’s `uuid`. To match the reply to any of the merged messages, use [`user_message_uuids`](#user_message_uuids).
- **A message you sent with `isSynthetic: true`**: the turn answers that message at first. If Claude Code picks up a regular message of yours between tool calls, the turn answers the picked-up message from then on. Echoing a synthetic message’s `uuid` requires Agent SDK v0.3.265 or later; earlier versions echo nothing on synthetic turns.
- **The prompt Claude Code generates to continue an interrupted turn under [`CLAUDE_CODE_RESUME_INTERRUPTED_TURN`](../02-Claude-Code-CLI/env-vars.md)**: when the interrupted turn’s last prompt is a regular message you sent, whether it opened the turn or Claude Code picked it up during the turn, the continued turn answers that message at first. [`resume_reason`](#resume_reason) tells the continued turn’s frames from the interrupted attempt’s. When the last prompt isn’t a regular message of yours, the continued turn answers no message of yours at first. If Claude Code picks up a regular message of yours between tool calls, the turn answers the picked-up message from then on. Echoing the interrupted turn’s prompt requires Agent SDK v0.3.268 or later.
- **Any other prompt Claude Code generated itself**: the turn answers no message of yours at first and its frames carry no echo. If Claude Code picks up a regular message of yours between tool calls, the turn answers that message from then on. The pickup echo requires Agent SDK v0.3.265 or later; earlier versions echo nothing on these turns.

Claude Code echoes the answered message’s `uuid` on three kinds of frame:

- **The result**: every result of a turn that answered a message you sent. Every such result carries it on Agent SDK v0.3.265 or later. Before v0.3.265, the success result of a turn that a regular message started lacked it when the turn sent no API request or ended with a deferred tool call. Before v0.3.246, error results lacked it too, and before v0.3.216 every result did.
- **The turn’s first reply**: the first [assistant message](#sdkassistantmessage), and with `includePartialMessages` also the first [stream event](#sdkpartialassistantmessage) whose `event.type` isn’t `ping`, so you can bind the reply before the result arrives. The first-reply echo requires Agent SDK v0.3.246 or later. Before v0.3.269, with `includePartialMessages`, Claude Code set it on that first stream event only, or on the first assistant message when the turn streamed nothing. When the message the turn is answering changes mid-turn, the first reply after the change carries the field too, on Agent SDK v0.3.265 or later; earlier versions set it on one reply frame per turn.
- **Every [`thinking_tokens`](#sdkthinkingtokensmessage) frame of the turn**: so you can attribute thinking progress to the message you sent without waiting for the turn’s first reply. Requires Agent SDK v0.3.260 or later.

Claude Code omits the field in these cases:

- Reply frames other than those first replies
- Subagent frames
- Turns that answer no message of yours, or answer a message you sent without a `uuid`
- Results that answer no message you sent, such as the zeroed result after a crashed worker process


[​](#user_message_uuids)

`user_message_uuids`

The `uuid`s of every message you sent that Claude Code answered in this turn. When you send several messages close together, Claude Code can merge them into one turn, and `user_message_uuid` then names only the last of them. To match the reply to any of the merged messages, look for that message’s `uuid` anywhere in this list. Requires Agent SDK v0.3.259 or later. Claude Code sets the list together with `user_message_uuid` on each reply frame that carries that field and on the result. For the full set of turn frames that echo the answered message’s `uuid`, and the version each requires, see [`user_message_uuid`](#user_message_uuid). The list always contains `user_message_uuid` and holds at most 64 entries. When Claude Code picks up a regular message you sent while a turn was running, it adds that message’s `uuid` to the result’s list. When a first reply or result carries `user_message_uuid` without the list, it came from an earlier Claude Code version, so fall back to the single field.


[​](#resume_reason)

`resume_reason`

Why this turn continues one that a restart interrupted. Claude Code sets this field on a turn that continues an interrupted one under [`CLAUDE_CODE_RESUME_INTERRUPTED_TURN`](../02-Claude-Code-CLI/env-vars.md), so you can tell the continued turn’s reply and result from the interrupted attempt’s. Requires Agent SDK v0.3.268 or later. Claude Code sets the field on two kinds of frame:

- **The continued turn’s result**: on the success and error arms alike, whether or not the result carries `user_message_uuid`.
- **The continued turn’s reply frames**: those that carry [`user_message_uuid`](#user_message_uuid).

The value is a short lowercase token, such as `interrupted_turn`.


[​](#queued_turn_count)

`queued_turn_count`

The number of messages you sent with [`origin: { kind: "human" }`](#sdkmessageorigin) that are still waiting in the command queue when Claude Code produced the result. Requires Agent SDK v0.3.242 or later. What `0` and an absent field tell you:

- **`0`**: Claude Code doesn’t count messages you sent without that `origin`, and doesn’t count task notifications, so a turn can still follow.
- **Absent**: the final result that Claude Code emits after a crash or fatal startup error omits the field, and [may carry zeroed totals](agent-sdk-cost-tracking.md#recover-totals-after-a-session-crash).


[​](#startup_failure_reason)

`startup_failure_reason`

Why Claude Code refused to start, so your application can offer the fix instead of a retry. Claude Code sets it on the `error_during_execution` result it writes before exiting on a known startup failure. That result carries zeroed totals, and its `errors` array carries the same text as stderr. The field is absent on every other result. Requires Agent SDK v0.3.274 or later. Set `CLAUDE_CODE_STARTUP_FAILURE_RESULTS` to `1` in [`env`](#options) to receive this result for every `SDKStartupFailureReason` value. Without that variable, Claude Code writes the result only for these failures, and the rest end with stderr output, a non-zero exit, and no result message:

- A resume that Claude Code stops because it [can’t return the session to its worktree](../02-Claude-Code-CLI/worktrees.md#the-session-resumes-outside-its-worktree), with `worktree_unverified` or `worktree_resume_refused`. That section says which error carries which value.
- A refused [`continue`](#options) of a conversation that a background session holds, with `session_held_by_background`. For a refused [`resume`](#options) of such a conversation, Claude Code writes the result only when the variable is set.

```python
type SDKStartupFailureReason =
  | "org_pin_api_key_conflict"
  | "provider_not_allowed"
  | "org_verify_failed"
  | "org_pin_mismatch"
  | "managed_settings_invalid"
  | "remote_settings_required_unavailable"
  | "gateway_signin_required"
  | "gateway_access_denied"
  | "proxy_invalid"
  | "temp_dir_unusable"
  | "cwd_unavailable"
  | "shell_tool_missing"
  | "session_held_by_background"
  | "worktree_resume_refused"
  | "worktree_unverified"
  | "cli_version_too_old"
  | "bypass_root"
  | "org_config_required_unavailable"
  | "org_config_refused";
```

Each value names one refusal:

| Value                                  | What stopped the session                                                                                                                                                                                                                                     |
|:---------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `org_pin_api_key_conflict`             | Managed settings [require a first-party or Cloud gateway sign-in](../02-Claude-Code-CLI/authentication.md#restrict-login-to-your-organization), and an Anthropic API key, auth token, or `apiKeyHelper` is configured instead                                                |
| `provider_not_allowed`                 | Managed settings [list the API providers this machine may use](../02-Claude-Code-CLI/settings-reference.md#allowedproviders), and the session is set up for a provider that isn’t listed, or for an endpoint the settings don’t pin. Requires Claude Code v2.1.285 or later  |
| `org_verify_failed`                    | The sign-in’s organization couldn’t be verified against the pin, for example because of a network failure or a revoked token                                                                                                                                 |
| `org_pin_mismatch`                     | The sign-in belongs to an organization the pin doesn’t allow                                                                                                                                                                                                 |
| `managed_settings_invalid`             | Managed policy settings couldn’t be read, the pin names no organization, or [managed model restrictions](../02-Claude-Code-CLI/errors.md#managed-settings-block-the-default-model) leave no permitted model for the Default option                                           |
| `remote_settings_required_unavailable` | Managed settings that the organization requires couldn’t be loaded                                                                                                                                                                                           |
| `gateway_signin_required`              | The [Cloud gateway](../13-Enterprise-Admin/claude-apps-gateway.md) ended this sign-in                                                                                                                                                                                         |
| `gateway_access_denied`                | The managed settings request to the Cloud gateway came back with a 403, which the gateway’s [troubleshooting table](../13-Enterprise-Admin/claude-apps-gateway-deploy.md#troubleshooting) covers                                                                              |
| `proxy_invalid`                        | A proxy setting isn’t a complete URL                                                                                                                                                                                                                         |
| `temp_dir_unusable`                    | The per-user temporary directory is unsafe or couldn’t be created                                                                                                                                                                                            |
| `cwd_unavailable`                      | The working directory was deleted, moved, or can’t be read                                                                                                                                                                                                   |
| `shell_tool_missing`                   | On Windows, no shell tool is available: Git Bash is missing, and PowerShell is missing or turned off with `CLAUDE_CODE_USE_POWERSHELL_TOOL`                                                                                                                  |
| `session_held_by_background`           | The conversation to resume or continue is running as a [background session](../09-Agents-Patterns/agent-view.md)                                                                                                                                                             |
| `worktree_resume_refused`              | The session’s worktree failed its safety checks, or the resume was launched from inside it. `errors` says whether running the same resume again continues without the worktree                                                                               |
| `worktree_unverified`                  | The session’s worktree couldn’t be verified right now, and retrying may succeed                                                                                                                                                                              |
| `cli_version_too_old`                  | This Claude Code version is below the minimum Anthropic requires                                                                                                                                                                                             |
| `bypass_root`                          | Bypass permissions mode was requested while running as root                                                                                                                                                                                                  |
| `org_config_required_unavailable`      | The session needs the organization’s policies and managed settings before it can start, and they couldn’t be loaded, for example because of a network failure or an Anthropic server error. Requires Agent SDK v0.3.293 or later                             |
| `org_config_refused`                   | Anthropic refused to provide the organization’s policies and managed settings for this sign-in, for example because the sign-in expired or was revoked, or the organization doesn’t allow Claude Code for this account. Requires Agent SDK v0.3.293 or later |


[​](#sdksystemmessage)

`SDKSystemMessage`

System initialization message.

```python
type SDKSystemMessage = {
  type: "system";
  subtype: "init";
  uuid: UUID;
  session_id: string;
  agents?: string[];
  apiKeySource: ApiKeySource;
  betas?: string[];
  claude_code_version: string;
  cwd: string;
  tools: string[];
  mcp_servers: {
    name: string;
    status: string;
    source?: string;
  }[];
  model: string;
  permissionMode: PermissionMode;
  slash_commands: string[];
  terminal_slash_commands?: string[];
  output_style: string;
  skills: string[];
  plugins: { name: string; path: string }[];
  plugin_errors?: {
    plugin: string;
    type: string;
    message: string;
    path?: string;
  }[];
  fast_mode_state?: FastModeState;
  fast_mode_disabled_reason?: FastModeDisabledReason;
  effort?: "low" | "medium" | "high" | "xhigh" | "max" | null;
  capabilities?: string[];
};
```

`fast_mode_state` reports the session’s [fast mode](../02-Claude-Code-CLI/fast-mode.md) state. When something blocks fast mode, `fast_mode_disabled_reason` names the check that blocked it; the field requires Claude Code v2.1.219 or later. For the reason codes and their meanings, see [`fast_mode_disabled_reason`](#sdkresultmessage) on the result message. `terminal_slash_commands` names the entries in `slash_commands` whose interface is bound to the local terminal, such as `exit`. You can send them like any other entry in `slash_commands`; the field exists so a remote or mobile client can hide them from its command menus. The field is present only when non-empty, and requires Agent SDK v0.3.229 or later.

- `source` on each `mcp_servers` entry: where the server’s definition came from, with the same values as [`McpServerStatus`](#mcpserverstatus)’s `source`. Requires Agent SDK v0.3.274 or later.
- `effort`: the [effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) Claude Code sends on the session’s next request, or `null` when it sends none. Claude Code sets the field only on the init message it sends to [Remote Control](../02-Claude-Code-CLI/remote-control.md) clients, and omits it from the init message your application reads. Requires Agent SDK v0.3.234 or later.

The `capabilities` array names the protocol behaviors this CLI implements, so you can feature-detect instead of comparing `claude_code_version` strings. It is an open set: ignore values you don’t recognize, and check for the specific capability whose behavior you rely on. The field requires Claude Code v2.1.205 or later and is absent on earlier CLIs.

| Capability                   | Meaning                                                                                                                                                                                                                                                                                           |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `interrupt_receipt_v1`       | [`interrupt()`](#query-object) resolves with an [`SDKControlInterruptResponse`](#sdkcontrolinterruptresponse) receipt listing the messages that were pending when the interrupt arrived                                                                                                           |
| `interrupt_cancel_queued_v1` | The `interrupt` control request honors `cancel_queued: true`, cancelling the messages the receipt would otherwise list under `still_queued` and listing them under `cancelled` instead. See [`SDKControlInterruptResponse`](#sdkcontrolinterruptresponse). Requires Claude Code v2.1.219 or later |
| `sdk_mcp_manifests`          | The `initialize` control request accepts `sdkMcpServerManifests`, MCP handshake results captured from your in-process [SDK MCP servers](agent-sdk-custom-tools.md). Claude Code advertises this capability in v2.1.286 or later                                                             |
| `sdk_mcp_tools_list_changed` | A `tools/list_changed` notification from an [SDK MCP server](agent-sdk-custom-tools.md) makes Claude Code list that server’s tools again, so a tool the server adds mid-session reaches Claude. Claude Code advertises this capability in v2.1.286 or later                                 |

The `plugin_errors` array lists plugin load failures. An entry describes either a plugin that didn’t load and is absent from `plugins`, or a plugin that loaded without one of its parts, such as its hooks file. The key is omitted when nothing failed. `SDKSystemMessage` declares `plugin_errors` in Agent SDK v0.3.283 or later. When a directory or archive from your [`plugins` option](#options) itself fails to load, the entry’s `plugin` field holds a positional tag such as `inline[0]` instead of a plugin name. This happens, for example, when the path doesn’t exist or the manifest is invalid. Match such an entry to your option by its `path` field. The table below lists the fields of each `plugin_errors` entry.

| Field     | Type     | Description                                                                                                                                                                               |
|-----------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `plugin`  | `string` | The failing plugin’s ID, or a positional tag such as `inline[0]` when the plugin directory or archive itself failed to load                                                               |
| `type`    | `string` | Error category from an open set, such as `path-not-found` or `manifest-validation-error`. Treat a value you don’t recognize as a generic failure                                          |
| `message` | `string` | Display text describing the failure                                                                                                                                                       |
| `path`    | `string` | Present only when the plugin directory or archive itself failed to load. Its absolute path, with a relative path from your `plugins` option resolved against the [`cwd`](#options) option |


[​](#sdkpartialassistantmessage)

`SDKPartialAssistantMessage`

Streaming partial message (only when `includePartialMessages` is true). The `parent_tool_use_id` field is always `null`: stream events are emitted for the main session only. For subagent attribution, use complete messages, which carry [`agent_id`](#sdkassistantmessage) and `parent_tool_use_id`, or enable [`forwardSubagentText`](#options) to receive subagent text and thinking as complete messages.

```python
type SDKPartialAssistantMessage = {
  type: "stream_event";
  event: BetaRawMessageStreamEvent; // From Anthropic SDK
  parent_tool_use_id: string | null;
  uuid: UUID;
  session_id: string;
  ttft_ms?: number; // Time to first token in ms, present only on message_start events
  user_message_uuid?: string;
  user_message_uuids?: string[];
  resume_reason?: string;
};
```

Claude Code sets `user_message_uuid` and `user_message_uuids` on the turn’s first non-ping stream event, and again when the message that the turn is answering changes, under the conditions in [`user_message_uuid`](#user_message_uuid). When the turn continues one that a restart interrupted, the stream events that carry those fields also carry [`resume_reason`](#resume_reason).


[​](#sdkcompactboundarymessage)

`SDKCompactBoundaryMessage`

Message indicating a conversation compaction boundary.

```python
type SDKCompactBoundaryMessage = {
  type: "system";
  subtype: "compact_boundary";
  uuid: UUID;
  session_id: string;
  compact_metadata: {
    trigger: "manual" | "auto";
    pre_tokens: number;
  };
};
```


[​](#sdkinformationalmessage)

`SDKInformationalMessage`

Generic text banner emitted by the loop. Carries warnings, notices, and other non-error status lines Claude Code raises, and hook feedback such as a `UserPromptSubmit` hook’s block reason. On Claude Code v2.1.227 or later, a hook’s [`systemMessage`](../07-Hooks/hooks.md#json-output) can arrive as this message, with each line prefixed by the hook’s name, such as `PostToolUse:Bash says:`. Each [event’s section](../07-Hooks/hooks.md#hook-events) on the hooks page says how output surfaces. Render `content` as plaintext at the given `level`.

```python
type SDKInformationalMessage = {
  type: "system";
  subtype: "informational";
  content: string;
  level: "info" | "notice" | "suggestion" | "warning";
  tool_use_id?: string;
  prevent_continuation?: boolean;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkworkershuttingdownmessage)

`SDKWorkerShuttingDownMessage`

Emitted on graceful worker teardown so remote clients can show why the worker exited instead of waiting for heartbeat timeout. The `reason` is a short snake_case string set by the host CLI, such as `"host_exit"` or `"remote_control_disabled"`. Act on this only when streaming live. A resumed session replays past instances of this message, so ignore them in that case.

```python
type SDKWorkerShuttingDownMessage = {
  type: "system";
  subtype: "worker_shutting_down";
  reason: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkplugininstallmessage)

`SDKPluginInstallMessage`

Plugin installation progress event. Emitted when [`CLAUDE_CODE_SYNC_PLUGIN_INSTALL`](../02-Claude-Code-CLI/env-vars.md) is set, so your Agent SDK application can track marketplace plugin installation before the first turn. The `started` and `completed` statuses bracket the overall install. The `installed` and `failed` statuses report individual marketplaces and include `name`.

```python
type SDKPluginInstallMessage = {
  type: "system";
  subtype: "plugin_install";
  status: "started" | "installed" | "failed" | "completed";
  name?: string;
  error?: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkpermissiondeniedmessage)

`SDKPermissionDeniedMessage`

Stream event emitted when the permission system denies a tool call without an interactive prompt. Use it to render the denial in your UI as it happens, rather than only observing the `is_error` tool result that follows. Which denials it reports depends on how the run handles permission prompts:

- **With a [`canUseTool`](#canusetool) callback** and the default [`permissionPrompts: 'host'`](#options): permission prompts go to your callback, and this event reports the denials Claude Code decides on its own without calling it.
- **With neither**: a bare `-p` run, or `query()` that sets neither `canUseTool` nor `permissionPromptToolName`, denies any tool call that would have prompted unless a [`PermissionRequest` hook](../07-Hooks/hooks-guide.md#limitations) allows it, and this event reports those denials as well as the ones Claude Code decides on its own. Before v2.1.223, Claude Code didn’t emit this event in runs without a callback.
- **With an MCP prompt tool**, set with `permissionPromptToolName` or the [`--permission-prompt-tool`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) flag, and the default `permissionPrompts: 'host'`: Claude Code doesn’t emit this event at all, not even for the rule denials it decides on its own.
- **With [`permissionPrompts: 'none'`](#options)**: Claude Code denies the calls that would have prompted, even when `canUseTool` or an MCP prompt tool is also set, and this event reports those denials as well as the ones Claude Code decides on its own. Requires Claude Code v2.1.259 or later.

In every configuration, this event skips any denial decided on the `PreToolUse` hook path, whether the hook denied the call itself or a deny rule overrode the hook’s allow or ask decision. The event is also best-effort: occasionally Claude Code records a denial without emitting this event, so `permission_denials` on the [result message](#sdkresultmessage) is the authoritative record.

```python
type SDKPermissionDeniedMessage = {
  type: "system";
  subtype: "permission_denied";
  tool_name: string;
  tool_use_id: string;
  agent_id?: string;
  decision_reason_type?: string;
  decision_reason?: string;
  message: string;
  uuid: UUID;
  session_id: string;
};
```

| Field                  | Type     | Description                                                                                                              |
|------------------------|----------|--------------------------------------------------------------------------------------------------------------------------|
| `tool_name`            | `string` | Name of the tool that was denied                                                                                         |
| `tool_use_id`          | `string` | ID of the `tool_use` block this denial answers                                                                           |
| `agent_id`             | `string` | Subagent ID when the denied call originated inside a subagent. Mirrors the field on `can_use_tool` for host-side routing |
| `decision_reason_type` | `string` | Discriminator for the component that decided, such as `"rule"`, `"mode"`, `"classifier"`, or `"asyncAgent"`              |
| `decision_reason`      | `string` | Human-readable reason from the deciding component, when available                                                        |
| `message`              | `string` | Rejection message returned to the model in the `tool_result`                                                             |


[​](#sdkpermissiondenial)

`SDKPermissionDenial`

Information about a denied tool use.

```python
type SDKPermissionDenial = {
  tool_name: string;
  tool_use_id: string;
  tool_input: Record<string, unknown>;
};
```


[​](#sdkcontextusage)

`SDKContextUsage`

Structured form of the `/context` report, carried as `context_usage` on the [`SDKAssistantMessage`](#sdkassistantmessage) that delivers a `/context` result. Agent SDK v0.3.232 and later export the type. Unlike [`SDKControlGetContextUsageResponse`](#sdkcontrolgetcontextusageresponse), it carries only the data needed to render the usage breakdown, without display fields such as `color` and `gridRows`. Claude Code computes the report with token-counting API requests that don’t appear in the message stream; see [how these requests are handled](#sdkcontrolgetcontextusageresponse).

```python
type SDKContextUsage = {
  model: string;
  total_tokens: number;
  raw_max_tokens: number;
  percentage: number;
  over_limit?: {
    tokens_over: number;
    kind: "hard_limit" | "compaction_window";
  };
  categories: SDKContextUsageCategory[];
  mcp_tools: {
    name: string;
    server_name: string;
    tokens: number;
  }[];
  memory_files: {
    path: string;
    type: string;
    tokens: number;
  }[];
  agents: {
    agent_type: string;
    source: string;
    tokens: number;
  }[];
  skills?: {
    name: string;
    source: string;
    plugin_name?: string;
    tokens: number;
  }[];
};
```

The table lists what Claude Code puts in each field. The fields from `model` through `over_limit` describe the session as a whole, and the collection fields attribute tokens to individual items.

| Field            | Type                                                      | Description                                                                                                                                                                                                                                                                                            |
|------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `model`          | `string`                                                  | The main loop’s model Claude Code computed the usage for, not a subagent’s                                                                                                                                                                                                                             |
| `total_tokens`   | `number`                                                  | Claude Code’s estimate of the tokens in use. Not clamped to the window, so it can exceed `raw_max_tokens` when the session is over the limit                                                                                                                                                           |
| `raw_max_tokens` | `number`                                                  | The model’s context window, or the lower [auto-compact window](../02-Claude-Code-CLI/model-config.md#context-window-and-auto-compaction) when one applies, such as one you set or the 200K boundary Claude Code applies to some models with a 1M-token window. Claude Code measures `total_tokens` against this window |
| `percentage`     | `number`                                                  | `total_tokens` as a rounded percentage of `raw_max_tokens`, so it can exceed 100 when the session is over the limit                                                                                                                                                                                    |
| `over_limit`     | `object`                                                  | Present only when `total_tokens` exceeds `raw_max_tokens`. `tokens_over` is the amount over, and `kind` says how Claude Code resolved the window                                                                                                                                                       |
| `categories`     | [`SDKContextUsageCategory`](#sdkcontextusagecategory)`[]` | One entry per row of the usage-by-category breakdown                                                                                                                                                                                                                                                   |
| `mcp_tools`      | `object[]`                                                | Tokens attributed to each MCP tool, with its wire name, such as `mcp__linear__create_issue`, and its `server_name`                                                                                                                                                                                     |
| `memory_files`   | `object[]`                                                | Tokens attributed to each loaded memory file, with its `path` and a source label such as `Project` or `User` in `type`                                                                                                                                                                                 |
| `agents`         | `object[]`                                                | Tokens attributed to each custom subagent definition, with a source identifier such as `projectSettings`, `userSettings`, or `plugin`. Built-in subagents aren’t listed                                                                                                                                |
| `skills`         | `object[]`                                                | Tokens attributed to each skill in the skill listing, with a source identifier and, for plugin skills, the plugin’s name in `plugin_name`. Absent when no skills contribute tokens                                                                                                                     |

`over_limit.kind` records how Claude Code resolved the window, not whether the API accepts the next request:

- `hard_limit`: the window is what Claude Code believes to be the model’s own limit, past which the API refuses requests
- `compaction_window`: the window is a compaction-policy window, which may or may not coincide with the model’s limit

Claude Code evolves the type additively, adding new data as optional fields rather than reshaping existing ones. Read the fields you know and ignore any you don’t recognize.


[​](#sdkcontextusagecategory)

`SDKContextUsageCategory`

One row of the `/context` usage-by-category breakdown.

```python
type SDKContextUsageCategory = {
  name: string;
  tokens: number;
  kind: "used" | "free" | "buffer" | "deferred";
};
```

The table lists what Claude Code puts in each field of a row.

| Field    | Type     | Description                                                                                              |
|----------|----------|----------------------------------------------------------------------------------------------------------|
| `name`   | `string` | The row’s display name as `/context` prints it, such as `Messages`. Classify rows by `kind`, not by name |
| `tokens` | `number` | The row’s token count. Rows can carry zero tokens                                                        |
| `kind`   | `string` | What the row represents: `used`, `free`, `buffer`, or `deferred`                                         |

Each `kind` value says what the row’s tokens are:

- `used`: content that occupies the context window
- `free`: the remaining window
- `buffer`: the compaction reserve
- `deferred`: tool schemas Claude Code holds out of the window and excludes from the usage calculation, listed for awareness


[​](#sdkusagereport)

`SDKUsageReport`

Structured form of the `/usage` report, carried as `usage_report` on the [`SDKAssistantMessage`](#sdkassistantmessage) that delivers a `/usage` result. Agent SDK v0.3.273 and later export the type. The type is experimental: its shape may change.

```python
type SDKUsageReport = {
  session: {
    total_cost_usd: number;
    total_api_duration_ms: number;
    total_duration_ms: number;
    total_lines_added: number;
    total_lines_removed: number;
    model_usage: { [modelName: string]: ModelUsage };
  };
  rate_limits: {
    limits:
      | {
          kind: string;
          group: string;
          percent: number;
          resets_at: string | null;
          scope?: {
            model?: { display_name: string } | null;
            surface?: { display_name: string } | null;
          } | null;
          severity: string;
          is_active: boolean;
        }[]
      | null;
    extra_usage?: {
      is_enabled: boolean;
      monthly_limit: number | null;
      used_credits: number | null;
      utilization: number | null;
      currency?: string | null;
    } | null;
  } | null;
};
```

The top-level fields are `session` and `rate_limits`:

- `session`: Claude Code’s running cost and usage totals, read from the same ledger as `total_cost_usd` and `modelUsage` on [`SDKResultMessage`](#sdkresultmessage). Each `model_usage` entry is a [`ModelUsage`](#modelusage).
- `rate_limits`: the plan’s usage rows in `limits` and usage-credits spend in `extra_usage`. It is `null` when Claude Code couldn’t get the plan’s usage, for example when the session’s OAuth token lacks the `user:profile` scope.

Claude Code computes `session.total_cost_usd` locally from token counts, so it is an estimate and not what your plan bills. The usage-credits spend the server reports is the separate `extra_usage` block. See [Track cost and usage](agent-sdk-cost-tracking.md) for the accuracy caveats. `limits` holds the server’s usage rows as the server sent them: which meters apply, their scope, labels, severity, and order are the server’s, so render the rows verbatim.

- An empty array means the server reported no meters.
- `null` means Claude Code has no rows to report.

Each row of `limits` describes one usage meter:

| Field       | Type            | Description                                                                                                            |
|-------------|-----------------|------------------------------------------------------------------------------------------------------------------------|
| `kind`      | `string`        | The server’s meter kind, such as `session`, `weekly_all`, or `weekly_scoped`. Classify a row on this, never on a label |
| `group`     | `string`        | The server’s row group, such as `session` or `weekly`. Rows render grouped under it, in the server’s order             |
| `percent`   | `number`        | Share of the window used, 0-100                                                                                        |
| `resets_at` | `string | null` | ISO 8601 timestamp when the window resets                                                                              |
| `scope`     | `object | null` | Optional. What a scoped row is for, a model or a surface, with the server’s display label                              |
| `severity`  | `string`        | The server’s reading of the row for a meter’s color, such as `normal`, `warning`, or `critical`                        |
| `is_active` | `boolean`       | `true` on the row the server picks for a single-value indicator to show                                                |

Before Agent SDK v0.3.277, the type declared `severity` and `is_active` as optional and nullable, and a row could arrive without them. `extra_usage` is the [usage credits](https://support.claude.com/en/articles/12429409-extra-usage-for-paid-claude-plans) spend and cap for the billing period as the server reports them, present when the plan has usage credits. Amounts are in minor units of `currency`, cents for USD.

- `monthly_limit` is `null` when this account has no spending cap of its own. On Team and Enterprise plans, don’t render `null` as unlimited.
- `is_enabled` is `false` while usage credits can’t pay for requests.


[​](#sdkmessageorigin)

`SDKMessageOrigin`

Provenance of a user-role message. This appears as `origin` on [`SDKUserMessage`](#sdkusermessage) and is forwarded onto the corresponding [`SDKResultMessage`](#sdkresultmessage) so you can tell what triggered a given turn.

```python
type SDKMessageOrigin =
  | { kind: "human" }
  | { kind: "channel"; server: string }
  | {
      kind: "peer";
      from: string;
      fromMode?: "bypass" | "prompting";
      name?: string;
      fromSession?: string;
      senderTaskId?: string;
      body?: string;
      verifiedPeerPid?: number;
    }
  | {
      kind: "task-notification";
      subkind?: "scheduled-trigger" | "peer-send-message";
      fireReason?: string;
    }
  | { kind: "coordinator" }
  | { kind: "auto-continuation" }
  | { kind: "unclassified" };
```

| `kind`              | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `human`             | Direct input from the end user. If your application forwards what the user typed as a user message, set its `origin` to `{ kind: "human" }` explicitly: Claude Code treats a user message with no `origin` as unattributed, and checks that require a human-typed prompt, such as the [`ultracode` workflow keyword](../02-Claude-Code-CLI/workflows.md#ask-for-a-workflow-in-your-prompt), don’t accept it. Before v2.1.210, Claude Code treated an absent `origin` on a user message as human input. |
| `channel`           | Message arriving on a [channel](../02-Claude-Code-CLI/channels.md). `server` is the source MCP server name.                                                                                                                                                                                                                                                                                                                                                                                            |
| `peer`              | Message from another agent: an in-process [teammate](../09-Agents-Patterns/agent-teams.md) or a [cross-session peer](../02-Claude-Code-CLI/cross-session-messaging.md), another of your Claude Code sessions. See [Peer origin fields](#peer-origin-fields) for the per-field semantics and the trust model.                                                                                                                                                                                                           |
| `task-notification` | Synthetic turn injected for a delivery that arrives without a fresh user prompt, such as a finished background task; see [`SDKTaskNotificationMessage`](#sdktasknotificationmessage) for that arm. A prompt your application [declares as a scheduled run](#declare-a-scheduled-run) carries this kind too. The optional `subkind` marks what raised the notification. See [Task-notification subkinds](#task-notification-subkinds).                                                  |
| `coordinator`       | Message from a team coordinator in an [agent team](../09-Agents-Patterns/agent-teams.md).                                                                                                                                                                                                                                                                                                                                                                                                              |
| `auto-continuation` | Synthetic turn injected when the session continues without fresh user input, such as a command result that triggers a follow-up prompt.                                                                                                                                                                                                                                                                                                                                                |
| `unclassified`      | Injected turn whose origin couldn’t be determined. Requires Claude Code v2.1.223 or later. When Claude Code receives an [`SDKUserMessage`](#sdkusermessage) with `isSynthetic: true` and can’t classify it as any other `kind`, it sets this kind as the message arrives and frames the turn to the model as a non-user source rather than treating it as human input. Your application shouldn’t set this value.                                                                      |


[​](#task-notification-subkinds)

Task-notification subkinds

When Claude Code delivers a task notification into a session, it sets `subkind` on the notification’s `origin` if Anthropic servers verified where that notification came from. It also sets `subkind` when your application [declares the message as a scheduled run](#declare-a-scheduled-run) itself, which requires TypeScript Agent SDK v0.3.280 or later. `subkind` requires Claude Code v2.1.213 or later, and it takes one of two values:

- `scheduled-trigger`: the notification is a [routine](../02-Claude-Code-CLI/web-scheduled-tasks.md)’s stored prompt, delivered because one of the routine’s triggers fired: its schedule, its [API trigger](../02-Claude-Code-CLI/web-scheduled-tasks.md#add-an-api-trigger), its [GitHub trigger](../02-Claude-Code-CLI/web-scheduled-tasks.md#add-a-github-trigger), or **Run now**. A prompt your application [declares as a scheduled run](#declare-a-scheduled-run) carries this value too. Claude Code frames these to the model as the session’s assigned task, with a different notice from the [notice that other task notifications carry](#sdktasknotificationmessage).
- `peer-send-message`: the notification is a message that another of your sessions sent with the server-side `send_message` tool that [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) use to message each other, not the [cross-session `SendMessage` tool](../02-Claude-Code-CLI/cross-session-messaging.md), and Anthropic servers verified that both sessions belong to the same private group of sessions. Requires Claude Code v2.1.224 or later. A `send_message` delivery the servers didn’t verify that way gets no subkind.

Every other task notification has no `subkind`. That includes [PR activity](../02-Claude-Code-CLI/claude-code-on-the-web.md#how-claude-responds-to-pr-activity) delivered into a session and background events such as a finished task. Messages from the [cross-session `SendMessage` tool](../02-Claude-Code-CLI/cross-session-messaging.md) aren’t task notifications at all: whether they come from a session on the same machine or through Anthropic servers from another machine, Claude Code gives them `kind: "peer"` and the [peer origin fields](#peer-origin-fields). `fireReason` says why a `scheduled-trigger` notification fired, as a short lowercase token such as `scheduled`, `manual`, `retry`, `catch_up`, or `api`. Anthropic servers set it on a [routine](../02-Claude-Code-CLI/web-scheduled-tasks.md)’s deliveries, and your application sets it when it declares a scheduled run. It’s absent when neither sent one. Requires TypeScript Agent SDK v0.3.280 or later.


[​](#declare-a-scheduled-run)

Declare a scheduled run

If your application runs prompts on its own schedule, declare each run so Claude Code frames the turn to the model as a scheduled task rather than as live input from the user. Start the session with `CLAUDE_CODE_HOST_SCHEDULED_RUN` set to `1` in [`env`](#options), then send the run’s [`SDKUserMessage`](#sdkusermessage) with `origin: { kind: "task-notification", subkind: "scheduled-trigger", fireReason: "scheduled" }` and without `isSynthetic`. Claude Code ignores the declaration in a process started without that variable. It also ignores it in a process whose environment carries [`CLAUDECODE`](../02-Claude-Code-CLI/env-vars.md) or `CLAUDE_CODE_CHILD_SESSION`. Claude Code keeps `fireReason` only when the value is 1 to 32 lowercase letters or underscores. Requires TypeScript Agent SDK v0.3.280 or later.


[​](#peer-origin-fields)

Peer origin fields

A `peer` origin identifies which agent sent the message: an in-process [teammate](../09-Agents-Patterns/agent-teams.md) sending to `main` with `SendMessage`, or a [cross-session peer](../02-Claude-Code-CLI/cross-session-messaging.md), another of your Claude Code sessions. Cross-session peers require Claude Code v2.1.224 or later on macOS and Linux; see [cross-session messaging availability](../02-Claude-Code-CLI/cross-session-messaging.md#availability) for the native Windows requirement. A cross-session peer can run on the same machine, or on [another of your machines](../02-Claude-Code-CLI/cross-session-messaging.md#message-sessions-on-other-machines) or [in the cloud](../02-Claude-Code-CLI/claude-code-on-the-web.md) when its message arrives through Remote Control. The two kinds of sender fill the fields differently:

- `from`: the teammate’s name, or the sender address for a cross-session peer. For a [one-way cross-machine message](../02-Claude-Code-CLI/cross-session-messaging.md#message-sessions-on-other-machines), the sender has no reply address and `from` is `"unknown"`. The value is sender-authored; `verifiedPeerPid` is the verified identity.
- `fromMode`: the sending session’s permission class, `bypass` or `prompting`, declared by a host that relays a peer message between your sessions, such as the [desktop app](../16-Mobile-Desktop/desktop.md#work-across-sessions). Claude Code reads it in the receiving session when it applies the [inbound controls](../02-Claude-Code-CLI/cross-session-messaging.md#control-inbound-messages). Requires Agent SDK v0.3.234 or later.
- `senderTaskId`: the teammate’s task ID. Absent for a cross-session peer.
- `name`: the sender’s display name, normalized by Claude Code: it strips Unicode control, format, surrogate, and line or paragraph separator code points, then trims the result and caps it at 64 code points with an ellipsis. Requires Claude Code v2.1.205 or later.
- `body`: the decoded message body with the peer envelope stripped, byte-exact with what the model sees. Always present for a teammate message; for a cross-session peer, present only when the turn is exactly one peer envelope formed by Claude Code. Render `name` and `body` instead of re-parsing the message text. Requires Claude Code v2.1.205 or later.
- `fromSession`: the sender’s host-openable session ID, set by the sender’s host so your UI can link back to the sending session. Like `from`, it is sender-asserted: use it as a navigation target only, and don’t treat it as proof of the sender’s identity. Requires Claude Code v2.1.216 or later.
- `verifiedPeerPid`: the process ID of the process that connected to this session’s cross-session messaging socket, verified by the kernel and read from the connection itself, never from the payload. Use it, not `from`, to identify the sender: `from` is forgeable by any same-user process. The field is absent when Claude Code can’t verify it, such as on Windows or non-socket ingress, so an absent value means the sender is unverified. For relayed traffic it identifies the relay rather than the message’s author, and process IDs are recyclable, so treat it as provenance rather than an authentication token. Requires Claude Code v2.1.216 or later.


[​](#hook-types)

Hook Types

For a guide on using hooks with examples and common patterns, see the [Hooks guide](agent-sdk-hooks.md).


[​](#hookevent)

`HookEvent`

Available hook events.

```python
type HookEvent =
  | "PreToolUse"
  | "PostToolUse"
  | "PostToolUseFailure"
  | "PostToolBatch"
  | "Notification"
  | "UserPromptSubmit"
  | "UserPromptExpansion"
  | "SessionStart"
  | "SessionEnd"
  | "Stop"
  | "StopFailure"
  | "SubagentStart"
  | "SubagentStop"
  | "PreCompact"
  | "PostCompact"
  | "PreModelSwitch"
  | "PostModelSwitch"
  | "PermissionRequest"
  | "PermissionDenied"
  | "Setup"
  | "TeammateIdle"
  | "TaskCreated"
  | "TaskCompleted"
  | "Elicitation"
  | "ElicitationResult"
  | "ConfigChange"
  | "DirectoryAdded"
  | "WorktreeCreate"
  | "WorktreeRemove"
  | "InstructionsLoaded"
  | "CwdChanged"
  | "FileChanged"
  | "MessageDisplay";
```


[​](#hookcallback)

`HookCallback`

Hook callback function type.

```python
type HookCallback = (
  input: HookInput, // Union of all hook input types
  toolUseID: string | undefined,
  options: { signal: AbortSignal }
) => Promise<HookJSONOutput>;
```


[​](#hookcallbackmatcher)

`HookCallbackMatcher`

Hook configuration with optional matcher.

```python
interface HookCallbackMatcher {
  matcher?: string;
  hooks: HookCallback[];
  timeout?: number; // Timeout in seconds for all hooks in this matcher
}
```


[​](#hookinput)

`HookInput`

Union type of all hook input types.

```python
type HookInput =
  | PreToolUseHookInput
  | PostToolUseHookInput
  | PostToolUseFailureHookInput
  | PostToolBatchHookInput
  | PermissionDeniedHookInput
  | NotificationHookInput
  | UserPromptSubmitHookInput
  | UserPromptExpansionHookInput
  | SessionStartHookInput
  | SessionEndHookInput
  | StopHookInput
  | StopFailureHookInput
  | SubagentStartHookInput
  | SubagentStopHookInput
  | PreCompactHookInput
  | PostCompactHookInput
  | PreModelSwitchHookInput
  | PostModelSwitchHookInput
  | PermissionRequestHookInput
  | SetupHookInput
  | TeammateIdleHookInput
  | TaskCreatedHookInput
  | TaskCompletedHookInput
  | ElicitationHookInput
  | ElicitationResultHookInput
  | ConfigChangeHookInput
  | InstructionsLoadedHookInput
  | DirectoryAddedHookInput
  | WorktreeCreateHookInput
  | WorktreeRemoveHookInput
  | CwdChangedHookInput
  | FileChangedHookInput
  | MessageDisplayHookInput;
```


[​](#basehookinput)

`BaseHookInput`

Base interface that all hook input types extend.

```python
type BaseHookInput = {
  session_id: string;
  transcript_path: string;
  cwd: string;
  prompt_id?: string;
  permission_mode?: string;
  effort?: { level: string };
  agent_id?: string;
  agent_type?: string;
};
```

The `prompt_id` field is a UUID identifying the user prompt currently being processed. It matches the [`prompt.id` attribute on OpenTelemetry events](../13-Enterprise-Admin/monitoring-usage.md#event-correlation-attributes) and is absent until the first user input. Requires Claude Code v2.1.196 or later.


[​](#pretoolusehookinput)

`PreToolUseHookInput`

```python
type PreToolUseHookInput = BaseHookInput & {
  hook_event_name: "PreToolUse";
  tool_name: string;
  tool_input: unknown;
  tool_use_id: string;
  mcp_server?: McpServerProvenance;
};
```

`mcp_server` is present when the tool comes from an MCP server; see [`McpServerProvenance`](#mcpserverprovenance). The `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied` inputs carry the same field. The field requires Agent SDK v0.3.274 or later.


[​](#posttoolusehookinput)

`PostToolUseHookInput`

```python
type PostToolUseHookInput = BaseHookInput & {
  hook_event_name: "PostToolUse";
  tool_name: string;
  tool_input: unknown;
  tool_response: unknown;
  tool_use_id: string;
  duration_ms?: number;
  mcp_server?: McpServerProvenance;
};
```


[​](#posttoolusefailurehookinput)

`PostToolUseFailureHookInput`

```python
type PostToolUseFailureHookInput = BaseHookInput & {
  hook_event_name: "PostToolUseFailure";
  tool_name: string;
  tool_input: unknown;
  tool_use_id: string;
  error: string;
  is_interrupt?: boolean;
  duration_ms?: number;
  mcp_server?: McpServerProvenance;
};
```


[​](#posttoolbatchhookinput)

`PostToolBatchHookInput`

Fires once after every tool call in a batch has resolved, before the next model request. `tool_response` carries the serialized `tool_result` content the model sees; the shape differs from `PostToolUseHookInput`’s structured `Output` object.

```python
type PostToolBatchHookInput = BaseHookInput & {
  hook_event_name: "PostToolBatch";
  tool_calls: PostToolBatchToolCall[];
};

type PostToolBatchToolCall = {
  tool_name: string;
  tool_input: unknown;
  tool_use_id: string;
  tool_response?: unknown;
};
```


[​](#permissiondeniedhookinput)

`PermissionDeniedHookInput`

```python
type PermissionDeniedHookInput = BaseHookInput & {
  hook_event_name: "PermissionDenied";
  tool_name: string;
  tool_input: unknown;
  tool_use_id: string;
  reason: string;
  mcp_server?: McpServerProvenance;
};
```


[​](#notificationhookinput)

`NotificationHookInput`

```python
type NotificationHookInput = BaseHookInput & {
  hook_event_name: "Notification";
  message: string;
  title?: string;
  notification_type: string;
};
```


[​](#userpromptsubmithookinput)

`UserPromptSubmitHookInput`

```python
type UserPromptSubmitHookInput = BaseHookInput & {
  hook_event_name: "UserPromptSubmit";
  prompt: string;
  session_title?: string;
};
```


[​](#userpromptexpansionhookinput)

`UserPromptExpansionHookInput`

```python
type UserPromptExpansionHookInput = BaseHookInput & {
  hook_event_name: "UserPromptExpansion";
  expansion_type: "slash_command" | "mcp_prompt";
  command_name: string;
  command_args: string;
  command_source?: string;
  prompt: string;
};
```


[​](#sessionstarthookinput)

`SessionStartHookInput`

```python
type SessionStartHookInput = BaseHookInput & {
  hook_event_name: "SessionStart";
  source: "startup" | "resume" | "clear" | "compact" | "fork";
  agent_type?: string;
  model?: string;
  session_title?: string;
};
```


[​](#sessionendhookinput)

`SessionEndHookInput`

```python
type SessionEndHookInput = BaseHookInput & {
  hook_event_name: "SessionEnd";
  reason: ExitReason; // String from EXIT_REASONS array
};
```


[​](#stophookinput)

`StopHookInput`

```python
type StopHookInput = BaseHookInput & {
  hook_event_name: "Stop";
  stop_hook_active: boolean;
  last_assistant_message?: string;
  background_tasks?: BackgroundTaskSummary[];
  session_crons?: SessionCronSummary[];
};
```


[​](#stopfailurehookinput)

`StopFailureHookInput`

```python
type StopFailureHookInput = BaseHookInput & {
  hook_event_name: "StopFailure";
  error: SDKAssistantMessageError;
  error_details?: string;
  last_assistant_message?: string;
};
```


[​](#subagentstarthookinput)

`SubagentStartHookInput`

```python
type SubagentStartHookInput = BaseHookInput & {
  hook_event_name: "SubagentStart";
  agent_id: string;
  agent_type: string;
};
```


[​](#subagentstophookinput)

`SubagentStopHookInput`

```python
type SubagentStopHookInput = BaseHookInput & {
  hook_event_name: "SubagentStop";
  stop_hook_active: boolean;
  agent_id: string;
  agent_transcript_path: string;
  agent_type: string;
  last_assistant_message?: string;
  background_tasks?: BackgroundTaskSummary[];
  session_crons?: SessionCronSummary[];
};

type BackgroundTaskSummary = {
  id: string;
  type: string;
  status: string;
  description: string;
  command?: string;
  agent_type?: string;
  server?: string;
  tool?: string;
  name?: string;
};

type SessionCronSummary = {
  id: string;
  schedule: string;
  recurring: boolean;
  prompt: string;
};
```


[​](#precompacthookinput)

`PreCompactHookInput`

```python
type PreCompactHookInput = BaseHookInput & {
  hook_event_name: "PreCompact";
  trigger: "manual" | "auto";
  custom_instructions: string | null;
};
```


[​](#postcompacthookinput)

`PostCompactHookInput`

```python
type PostCompactHookInput = BaseHookInput & {
  hook_event_name: "PostCompact";
  trigger: "manual" | "auto";
  compact_summary: string;
};
```


[​](#premodelswitchhookinput)

`PreModelSwitchHookInput`

Fires before a requested model switch takes effect. `context_tokens` and the fields after it estimate what re-sending the conversation to the new model costs. For the full field descriptions and blocking semantics, see [PreModelSwitch](../07-Hooks/hooks.md#premodelswitch).

```python
type PreModelSwitchHookInput = BaseHookInput & {
  hook_event_name: "PreModelSwitch";
  from_model: string;
  to_model: string;
  requested_model: string | null;
  source: "command" | "picker" | "sdk";
  context_tokens: number;
  prompt_cache_warm: boolean;
  cache_ttl: "5m" | "1h";
  estimated_cache_write_usd: number;
  pricing: "configured" | "catalog" | "default";
};
```


[​](#postmodelswitchhookinput)

`PostModelSwitchHookInput`

Fires after the session’s model changes. It carries the same fields as `PreModelSwitchHookInput`, with two more `source` values. See [PostModelSwitch](../07-Hooks/hooks.md#postmodelswitch).

```python
type PostModelSwitchHookInput = BaseHookInput & {
  hook_event_name: "PostModelSwitch";
  from_model: string;
  to_model: string;
  requested_model: string | null;
  source: "command" | "picker" | "sdk" | "auto" | "resume";
  context_tokens: number;
  prompt_cache_warm: boolean;
  cache_ttl: "5m" | "1h";
  estimated_cache_write_usd: number;
  pricing: "configured" | "catalog" | "default";
};
```


[​](#permissionrequesthookinput)

`PermissionRequestHookInput`

```python
type PermissionRequestHookInput = BaseHookInput & {
  hook_event_name: "PermissionRequest";
  tool_name: string;
  tool_input: unknown;
  permission_suggestions?: PermissionUpdate[];
  mcp_server?: McpServerProvenance;
};
```


[​](#setuphookinput)

`SetupHookInput`

```python
type SetupHookInput = BaseHookInput & {
  hook_event_name: "Setup";
  trigger: "init" | "maintenance";
};
```


[​](#teammateidlehookinput)

`TeammateIdleHookInput`

```python
type TeammateIdleHookInput = BaseHookInput & {
  hook_event_name: "TeammateIdle";
  teammate_name: string;
  /** @deprecated since v2.1.178. Carries the session-derived team name; will be removed. */
  team_name: string;
};
```


[​](#taskcreatedhookinput)

`TaskCreatedHookInput`

```python
type TaskCreatedHookInput = BaseHookInput & {
  hook_event_name: "TaskCreated";
  task_id: string;
  task_subject: string;
  task_description?: string;
  teammate_name?: string;
  /** @deprecated since v2.1.178. Carries the session-derived team name; will be removed. */
  team_name?: string;
};
```


[​](#taskcompletedhookinput)

`TaskCompletedHookInput`

```python
type TaskCompletedHookInput = BaseHookInput & {
  hook_event_name: "TaskCompleted";
  task_id: string;
  task_subject: string;
  task_description?: string;
  teammate_name?: string;
  /** @deprecated since v2.1.178. Carries the session-derived team name; will be removed. */
  team_name?: string;
};
```


[​](#elicitationhookinput)

`ElicitationHookInput`

```python
type ElicitationHookInput = BaseHookInput & {
  hook_event_name: "Elicitation";
  mcp_server_name: string;
  message: string;
  mode?: "form" | "url";
  url?: string;
  elicitation_id?: string;
  requested_schema?: Record<string, unknown>;
};
```


[​](#elicitationresulthookinput)

`ElicitationResultHookInput`

```python
type ElicitationResultHookInput = BaseHookInput & {
  hook_event_name: "ElicitationResult";
  mcp_server_name: string;
  elicitation_id?: string;
  mode?: "form" | "url";
  action: "accept" | "decline" | "cancel";
  content?: Record<string, unknown>;
};
```


[​](#configchangehookinput)

`ConfigChangeHookInput`

```python
type ConfigChangeHookInput = BaseHookInput & {
  hook_event_name: "ConfigChange";
  source:
    | "user_settings"
    | "project_settings"
    | "local_settings"
    | "policy_settings"
    | "skills";
  file_path?: string;
};
```


[​](#instructionsloadedhookinput)

`InstructionsLoadedHookInput`

```python
type InstructionsLoadedHookInput = BaseHookInput & {
  hook_event_name: "InstructionsLoaded";
  file_path: string;
  memory_type: "User" | "Project" | "Local" | "Managed";
  load_reason:
    | "session_start"
    | "nested_traversal"
    | "path_glob_match"
    | "include"
    | "compact";
  globs?: string[];
  trigger_file_path?: string;
  parent_file_path?: string;
};
```


[​](#directoryaddedhookinput)

`DirectoryAddedHookInput`

```python
type DirectoryAddedHookInput = BaseHookInput & {
  hook_event_name: "DirectoryAdded";
  directory: string;
  source: "slash_command" | "register_repo_root";
};
```

`directory` is the absolute path of the directory that was added. `source` is `"slash_command"` when `/add-dir` added it and `"register_repo_root"` when the SDK control request did.


[​](#worktreecreatehookinput)

`WorktreeCreateHookInput`

```python
type WorktreeCreateHookInput = BaseHookInput & {
  hook_event_name: "WorktreeCreate";
  name: string;
};
```


[​](#worktreeremovehookinput)

`WorktreeRemoveHookInput`

```python
type WorktreeRemoveHookInput = BaseHookInput & {
  hook_event_name: "WorktreeRemove";
  worktree_path: string;
};
```


[​](#cwdchangedhookinput)

`CwdChangedHookInput`

```python
type CwdChangedHookInput = BaseHookInput & {
  hook_event_name: "CwdChanged";
  old_cwd: string;
  new_cwd: string;
};
```


[​](#filechangedhookinput)

`FileChangedHookInput`

```python
type FileChangedHookInput = BaseHookInput & {
  hook_event_name: "FileChanged";
  file_path: string;
  event: "change" | "add" | "unlink";
};
```


[​](#messagedisplayhookinput)

`MessageDisplayHookInput`

```python
type MessageDisplayHookInput = BaseHookInput & {
  hook_event_name: "MessageDisplay";
  turn_id: string;
  message_id: string;
  index: number;
  final: boolean;
  delta: string;
};
```


[​](#hookjsonoutput)

`HookJSONOutput`

Hook return value.

```python
type HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput;
```


[​](#asynchookjsonoutput)

`AsyncHookJSONOutput`

```python
type AsyncHookJSONOutput = {
  async: true;
  asyncTimeout?: number;
};
```


[​](#synchookjsonoutput)

`SyncHookJSONOutput`

```python
type SyncHookJSONOutput = {
  continue?: boolean;
  suppressOutput?: boolean;
  stopReason?: string;
  decision?: "approve" | "block";
  systemMessage?: string;
  /**
   * A terminal escape sequence (e.g. OSC 9 / OSC 777 desktop-notification)
   * for Claude Code to emit on your behalf. Only notification/title OSCs
   * (0, 1, 2, 9, 99, 777) and BEL are permitted; a value containing
   * anything else is ignored as a whole. Only the interactive CLI emits
   * it; the SDK ignores the field.
   */
  terminalSequence?: string;
  reason?: string;
  hookSpecificOutput?:
    | {
        hookEventName: "PreToolUse";
        permissionDecision?: "allow" | "deny" | "ask" | "defer";
        permissionDecisionReason?: string;
        updatedInput?: Record<string, unknown>;
        additionalContext?: string;
      }
    | {
        hookEventName: "UserPromptSubmit";
        additionalContext?: string;
        sessionTitle?: string;
        /** When decision is "block", omit the original prompt from the block message. */
        suppressOriginalPrompt?: boolean;
      }
    | {
        hookEventName: "UserPromptExpansion";
        additionalContext?: string;
      }
    | {
        hookEventName: "SessionStart";
        additionalContext?: string;
        initialUserMessage?: string;
        sessionTitle?: string;
        watchPaths?: string[];
        /**
         * Re-scan skill and command directories after SessionStart hooks
         * complete, so skills installed by the hook are available in the
         * same session.
         */
        reloadSkills?: boolean;
      }
    | {
        hookEventName: "Setup";
        additionalContext?: string;
      }
    | {
        hookEventName: "PreModelSwitch";
        /**
         * Same contract as PreToolUse: "allow" proceeds, "deny" cancels
         * the switch, "ask" asks the user to confirm. Only /model in an
         * interactive session shows that prompt; every other surface,
         * set_model requests included, treats "ask" as a refusal.
         */
        permissionDecision?: "allow" | "deny" | "ask";
        permissionDecisionReason?: string;
      }
    | {
        hookEventName: "PostModelSwitch";
        /** Reaches the model with the next request the new model serves. */
        additionalContext?: string;
      }
    | {
        hookEventName: "SubagentStart";
        additionalContext?: string;
      }
    | {
        hookEventName: "PostToolUse";
        additionalContext?: string;
        /**
         * Short note about this tool call's result for the auto mode
         * permission classifier. Capped at 2000 characters, shared across
         * all hooks that respond to the same call; honored on synchronous
         * hook responses only. Don't copy untrusted tool output into it.
         */
        classifierContext?: string;
        updatedToolOutput?: unknown;
        /** @deprecated Use `updatedToolOutput`, which works for all tools. */
        updatedMCPToolOutput?: unknown;
      }
    | {
        hookEventName: "PostToolUseFailure";
        additionalContext?: string;
      }
    | {
        hookEventName: "PostToolBatch";
        additionalContext?: string;
      }
    | {
        hookEventName: "Stop";
        additionalContext?: string;
      }
    | {
        hookEventName: "SubagentStop";
        additionalContext?: string;
      }
    | {
        hookEventName: "PermissionDenied";
        retry?: boolean;
      }
    | {
        hookEventName: "Notification";
        additionalContext?: string;
      }
    | {
        hookEventName: "PermissionRequest";
        decision:
          | {
              behavior: "allow";
              updatedInput?: Record<string, unknown>;
              updatedPermissions?: PermissionUpdate[];
            }
          | {
              behavior: "deny";
              message?: string;
              interrupt?: boolean;
            };
      }
    | {
        hookEventName: "Elicitation";
        action?: "accept" | "decline" | "cancel";
        content?: Record<string, unknown>;
      }
    | {
        hookEventName: "ElicitationResult";
        action?: "accept" | "decline" | "cancel";
        content?: Record<string, unknown>;
      }
    | {
        hookEventName: "CwdChanged";
        watchPaths?: string[];
      }
    | {
        hookEventName: "FileChanged";
        watchPaths?: string[];
      }
    | {
        hookEventName: "WorktreeCreate";
        worktreePath: string;
      }
    | {
        hookEventName: "MessageDisplay";
        /** Text displayed in place of the delta. Omit (or return the delta unchanged) to display the original. */
        displayContent?: string;
      };
};
```


[​](#tool-input-types)

Tool Input Types

Documentation of input schemas for all built-in Claude Code tools. These types are exported from `@anthropic-ai/claude-agent-sdk/sdk-tools` and can be used for type-safe tool interactions.


[​](#toolinputschemas)

`ToolInputSchemas`

Union of tool input types exported from `@anthropic-ai/claude-agent-sdk/sdk-tools`; members include:

```python
type ToolInputSchemas =
  | AgentInput
  | ArtifactInput
  | AskUserQuestionInput
  | BashInput
  | CronCreateInput
  | CronDeleteInput
  | CronListInput
  | EnterPlanModeInput
  | EnterWorktreeInput
  | ExitPlanModeInput
  | ExitWorktreeInput
  | FileEditInput
  | FileReadInput
  | FileWriteInput
  | GlobInput
  | GrepInput
  | ListMcpResourcesInput
  | McpInput
  | MonitorInput
  | NotebookEditInput
  | ProjectsInput
  | PushNotificationInput
  | ReadMcpResourceDirInput
  | ReadMcpResourceInput
  | RefreshMcpToolsInput
  | RemoteTriggerInput
  | ReportFindingsInput
  | ScheduleWakeupInput
  | ShowOnboardingRolePickerInput
  | TaskCreateInput
  | TaskGetInput
  | TaskListInput
  | TaskStopInput
  | TaskUpdateInput
  | TodoWriteInput
  | WebFetchInput
  | WebSearchInput
  | WorkflowInput;
```


[​](#agent)

Agent

**Tool name:** `Agent`. The previous name `Task` is still accepted as an alias, and the `tools` array in the [`SDKSystemMessage`](#sdksystemmessage) init message currently lists this tool as `Task` for backward compatibility.

The `mode` field is deprecated and ignored on Claude Code v2.1.212 or later. A subagent runs in either the parent session’s permission mode or its definition’s [`permissionMode`](#agentdefinition), and the [subagent inheritance rules](agent-sdk-permissions.md#available-modes) decide which.

```python
type AgentInput = {
  description: string;
  prompt: string;
  subagent_type?: string;
  model?: "sonnet" | "opus" | "haiku" | "fable";
  effort?: "low" | "medium" | "high" | "xhigh" | "max";
  run_in_background?: boolean;
  name?: string;
  team_name?: string; // Deprecated; ignored
  mode?: "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan"; // Deprecated; ignored. The subagent inheritance rules decide a subagent's permission mode
  isolation?: "worktree" | "remote";
};
```

Launches a new agent to handle complex, multi-step tasks autonomously.


[​](#askuserquestion)

AskUserQuestion

**Tool name:** `AskUserQuestion`

```python
type AskUserQuestionInput = {
  questions: Array<{
    question: string;
    header: string;
    options: Array<{ label: string; description: string; preview?: string }>;
    multiSelect: boolean;
  }>;
  answers?: Record<string, string>;
  annotations?: Record<string, { preview?: string; notes?: string }>;
  metadata?: { source?: string };
};
```

Asks the user clarifying questions during execution. See [Handle approvals and user input](agent-sdk-user-input.md#handle-clarifying-questions) for usage details.


[​](#bash)

Bash

**Tool name:** `Bash`

```python
type BashInput = {
  command: string;
  timeout?: number; // milliseconds. Foreground: capped at 600000 by default, higher values are clamped. With run_in_background (Claude Code v2.1.285 or later): the background time limit, 1800000 when omitted, capped at 7200000 unless raised
  description?: string;
  run_in_background?: boolean;
  dangerouslyDisableSandbox?: boolean;
};
```

Executes Bash commands with optional timeout and background execution. The working directory persists between commands, including commands run in later turns of a multi-turn session; shell state such as exported environment variables doesn’t. For the limits on which directory changes carry over, see [What persists between commands](../02-Claude-Code-CLI/tools-reference.md#what-persists-between-commands). For what sets the foreground ceiling, see [Timeout and output limits](../02-Claude-Code-CLI/tools-reference.md#timeout-and-output-limits). For the background time limit, see [Time limit for background commands](../02-Claude-Code-CLI/tools-reference.md#time-limit-for-background-commands).


[​](#monitor)

Monitor

**Tool name:** `Monitor`

```python
type MonitorInput = {
  description: string;
  timeout_ms: number;
  command?: string;
  ws?: {
    url: string;
    protocols?: string[];
  };
};
```

Runs a background source and delivers each event to Claude so it can react without polling: `command` runs a script and emits one event per stdout line, and `ws` opens a WebSocket and emits one event per text frame. Provide exactly one of `command` or `ws`. The `ws` source requires Claude Code v2.1.195 or later. `timeout_ms` is the watch’s deadline in milliseconds. It defaults to 300000 and accepts values up to 3600000. The effective deadline is at most 1800000, which is 30 minutes, so a larger accepted value is shortened to that. At the deadline the watch ends and Claude receives one notice so it can start a new watch if it still needs one. The exported type marks `timeout_ms` as required because the schema fills in the default; a call that omits it validates. When Monitor runs a command, it follows the same permission rules as Bash; a WebSocket watch prompts for approval separately. See the [Monitor tool reference](../02-Claude-Code-CLI/tools-reference.md#monitor-tool) for behavior and provider availability.


[​](#taskoutput)

TaskOutput

Removed in Claude Code v2.1.277, together with its `TaskOutputInput` type. Previously retrieved output from a running or completed background task; Claude reads a background task’s output file with `Read` instead. A `disallowedTools` entry or a deny rule that still names `TaskOutput` is ignored without a warning.


[​](#edit)

Edit

**Tool name:** `Edit`

```python
type FileEditInput = {
  file_path: string;
  old_string: string;
  new_string: string;
  replace_all?: boolean;
};
```

Performs exact string replacements in files.


[​](#read)

Read

**Tool name:** `Read`

```python
type FileReadInput = {
  file_path: string;
  offset?: number;
  limit?: number;
  pages?: string;
};
```

Reads files from the local filesystem, including text, images, PDFs, and Jupyter notebooks. Use `pages` for PDF page ranges (for example, `"1-5"`). For a PDF, Claude receives the file’s contents inside the Read call’s `tool_result` content. A read that returns the `pdf` [output](#tool-output-types) carries a summary `text` block followed by a `document` block. One that returns the `parts` output carries the summary `text` block followed by one block per extracted page: an `image` block, or a `text` block naming the page when Claude Code couldn’t render it as an image. Before Agent SDK v0.3.242, Claude Code delivered the file’s contents as a separate `user` message after the tool result.


[​](#write)

Write

**Tool name:** `Write`

```python
type FileWriteInput = {
  file_path: string;
  content: string;
};
```

Writes a file to the local filesystem, overwriting if it exists.


[​](#glob)

Glob

**Tool name:** `Glob`

```python
type GlobInput = {
  pattern: string;
  path?: string;
};
```

Fast file pattern matching that works with any codebase size.


[​](#grep)

Grep

**Tool name:** `Grep`

```python
type GrepInput = {
  pattern: string;
  path?: string;
  glob?: string;
  type?: string;
  output_mode?: "content" | "files_with_matches" | "count";
  "-i"?: boolean;
  "-o"?: boolean; // print only the matched parts of each line; requires output_mode: "content"
  "-n"?: boolean;
  "-B"?: number;
  "-A"?: number;
  "-C"?: number;
  context?: number;
  head_limit?: number;
  offset?: number;
  multiline?: boolean;
};
```

Search tool built on ripgrep with regex support.


[​](#taskstop)

TaskStop

**Tool name:** `TaskStop`

```python
type TaskStopInput = {
  task_id?: string;
  shell_id?: string; // Deprecated: use task_id
};
```

Stops a running background task or shell by ID. As of v2.1.198, `task_id` also accepts an agent-team teammate or a named background agent by agent ID or name.


[​](#notebookedit)

NotebookEdit

**Tool name:** `NotebookEdit`

```python
type NotebookEditInput = {
  notebook_path: string;
  cell_id?: string;
  new_source: string;
  cell_type?: "code" | "markdown";
  edit_mode?: "replace" | "insert" | "delete";
};
```

Edits cells in Jupyter notebook files.


[​](#webfetch)

WebFetch

**Tool name:** `WebFetch`

```python
type WebFetchInput = {
  url: string;
  prompt: string;
  offset?: number;
};
```

Fetches content from a URL and processes it with an AI model. `offset` is the number of characters to skip from the start of the page. Claude sets it to keep reading a long page. The field requires Agent SDK v0.3.290 or later.


[​](#websearch)

WebSearch

**Tool name:** `WebSearch`

```python
type WebSearchInput = {
  query: string;
  allowed_domains?: string[];
  blocked_domains?: string[];
};
```

Searches the web and returns formatted results.


[​](#workflow)

Workflow

**Tool name:** `Workflow`

```python
type WorkflowInput = {
  script?: string;
  name?: string;
  scriptPath?: string;
  args?: unknown; // any JSON value; the published typings render this as an object map
  resumeFromRunId?: string;
  title?: string; // ignored; the script's meta block sets the title
  description?: string; // ignored; the script's meta block sets the description
};
```

Runs a [dynamic workflow](../02-Claude-Code-CLI/workflows.md): a script that orchestrates many subagents in the background and returns one consolidated result. The `Workflow` tool is available in Agent SDK v0.3.149 and later. At least one of `script`, `name`, or `scriptPath` is required.

| Field             | Type      | Description                                                                                                                                                                                                                                                                          |
|-------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `script`          | `string`  | Inline workflow script. Must begin with `export const meta = { name, description }` as a literal, followed by the script body using `agent()`, `parallel()`, `pipeline()`, and `phase()`. An optional `phases` array in `meta` groups agents under named stages in the progress view |
| `name`            | `string`  | Name of a built-in workflow or one saved in `.claude/workflows/`. Resolved to a script                                                                                                                                                                                               |
| `scriptPath`      | `string`  | Path to a workflow script file on disk, such as the `scriptPath` a previous run returned. Takes precedence over `script` and `name`. Claude Code rejects `scriptPath` with an error when the session’s tools don’t include `Read`                                                    |
| `args`            | `unknown` | Input value exposed to the script as the global `args`, for parameterized named workflows such as a research question or a list of file paths. Pass arrays and objects as actual JSON values, not as a JSON-encoded string                                                           |
| `resumeFromRunId` | `string`  | Run ID of a prior `Workflow` invocation to resume. Completed `agent()` calls with unchanged inputs usually return cached results; the rest run live. [Resume after a pause](../02-Claude-Code-CLI/workflows.md#resume-after-a-pause) covers which completed calls re-run. Same session only          |
| `title`           | `string`  | Ignored; the script’s `meta` block sets the title                                                                                                                                                                                                                                    |
| `description`     | `string`  | Ignored; the script’s `meta` block sets the description                                                                                                                                                                                                                              |


[​](#todowrite)

TodoWrite

**Tool name:** `TodoWrite`

```python
type TodoWriteInput = {
  todos: Array<{
    content: string;
    status: "pending" | "in_progress" | "completed";
    activeForm: string;
  }>;
};
```

Creates and manages a structured task list for tracking progress.

The following tools are available by default only on Claude 3.x models, Opus 4 through 4.7, Sonnet 4 through 4.6, and Haiku 4.5. On every other model, including model IDs Claude Code doesn’t recognize, they aren’t available unless you opt in:

- `TodoWrite`
- `TaskCreate`
- `TaskGet`
- `TaskUpdate`
- `TaskList`

Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.See [Model availability](agent-sdk-todo-tracking.md#model-availability) to opt in.


[​](#taskcreate)

TaskCreate

**Tool name:** `TaskCreate`

```python
type TaskCreateInput = {
  subject: string;
  description: string;
  activeForm?: string;
  metadata?: Record<string, unknown>;
};
```

Creates a single task and returns its assigned ID.


[​](#taskupdate)

TaskUpdate

**Tool name:** `TaskUpdate`

```python
type TaskUpdateInput = {
  taskId: string;
  status?: "pending" | "in_progress" | "completed" | "deleted";
  subject?: string;
  description?: string;
  activeForm?: string;
  addBlocks?: string[];
  addBlockedBy?: string[];
  owner?: string;
  metadata?: Record<string, unknown>;
};
```

Patches one task by ID. Set `status` to `"deleted"` to remove it.


[​](#taskget)

TaskGet

**Tool name:** `TaskGet`

```python
type TaskGetInput = {
  taskId: string;
};
```

Returns full details for one task, or `null` when the ID is not found.


[​](#tasklist)

TaskList

**Tool name:** `TaskList`

```python
type TaskListInput = {};
```

Returns a snapshot of all tasks in the current list.


[​](#exitplanmode)

ExitPlanMode

**Tool name:** `ExitPlanMode`

```python
type ExitPlanModeInput = {
  /** Deprecated: no longer used. */
  allowedPrompts?: Array<{
    tool: "Bash";
    prompt: string;
  }>;
  [k: string]: unknown;
};
```

Exits plan mode. The `allowedPrompts` field is deprecated and ignored; Claude Code still accepts it so existing callers and transcripts validate. Before v2.1.205, it requested prompt-based Bash permissions for implementing the plan.


[​](#listmcpresources)

ListMcpResources

**Tool name:** `ListMcpResourcesTool`

```python
type ListMcpResourcesInput = {
  server?: string;
};
```

Lists available MCP resources from connected servers.


[​](#readmcpresource)

ReadMcpResource

**Tool name:** `ReadMcpResourceTool`

```python
type ReadMcpResourceInput = {
  server: string;
  uri: string;
};
```

Reads a specific MCP resource from a server.


[​](#enterworktree)

EnterWorktree

**Tool name:** `EnterWorktree`

```python
type EnterWorktreeInput = {
  name?: string;
  path?: string;
};
```

Creates and enters a temporary git worktree for isolated work. Pass `path` to switch into an existing worktree instead of creating a new one. On first entry the target must be a registered worktree of the current repository or, in a multi-repo workspace, of a repository nested inside it; from within a worktree session it must be under `.claude/worktrees/` of the session’s repository. `name` and `path` are mutually exclusive.


[​](#exitworktree)

ExitWorktree

**Tool name:** `ExitWorktree`

```python
type ExitWorktreeInput = {
  action: "keep" | "remove";
  discard_changes?: boolean;
};
```

Exits the current git worktree and returns to the original working directory. The `keep` action leaves the worktree and branch on disk, while `remove` deletes both. `discard_changes` must be `true` when removing a worktree that has uncommitted files or unmerged commits.


[​](#enterplanmode)

EnterPlanMode

**Tool name:** `EnterPlanMode`

```python
type EnterPlanModeInput = {};
```

Enters plan mode, where Claude researches and presents a plan before making changes.


[​](#croncreate)

CronCreate

**Tool name:** `CronCreate`

```python
type CronCreateInput = {
  cron: string;
  prompt: string;
  recurring?: boolean;
  durable?: boolean;
};
```

Schedules a prompt to run on a 5-field cron schedule in local time. Set `recurring` to `false` to fire once at the next match. Jobs are session-scoped by default, and resuming with `--resume` or `--continue` restores jobs that haven’t expired. See [Scheduled tasks](../02-Claude-Code-CLI/scheduled-tasks.md). Setting `durable` to `true` requests persistence to `.claude/scheduled_tasks.json` so the job survives restarts. Durable scheduling isn’t available in every session: when it isn’t, Claude Code accepts `durable: true` but creates the job session-only. Read the output’s `durable` field to see whether the job persisted.


[​](#crondelete)

CronDelete

**Tool name:** `CronDelete`

```python
type CronDeleteInput = {
  id: string;
};
```

Deletes a scheduled cron job by the ID returned from `CronCreate`.


[​](#cronlist)

CronList

**Tool name:** `CronList`

```python
type CronListInput = {};
```

Lists the scheduled cron jobs: durable jobs from `.claude/scheduled_tasks.json` and session-only jobs from the current session.


[​](#schedulewakeup)

ScheduleWakeup

**Tool name:** `ScheduleWakeup`

```python
type ScheduleWakeupInput = {
  delaySeconds?: number;
  reason?: string;
  prompt?: string;
  noop?: boolean;
  stop?: boolean;
};
```

Schedules a one-shot wake-up that fires the given prompt after a delay. This tool backs the self-paced `/loop` command. The runtime clamps `delaySeconds` to between 60 and 3600 seconds. The `delaySeconds`, `reason`, `prompt`, and `noop` fields are required unless `stop` is true. `noop: true` reports a wake-up where nothing changed. Setting `stop: true` cancels the pending wakeup and ends the self-paced `/loop`. The `stop` field requires Claude Code v2.1.202 or later. See the [ScheduleWakeup row in the tools reference](../02-Claude-Code-CLI/tools-reference.md).


[​](#remotetrigger)

RemoteTrigger

**Tool name:** `RemoteTrigger`

```python
type RemoteTriggerInput = {
  action:
    | "list"
    | "get"
    | "create"
    | "update"
    | "run"
    | "create_webhook_trigger"
    | "list_runs"
    | "get_run_log";
  trigger_id?: string;
  session_id?: string;
  cursor?: string;
  body?: {
    [k: string]: unknown;
  };
};
```

Manages [Routines](../02-Claude-Code-CLI/web-scheduled-tasks.md), the scheduled and triggered Claude Code runs hosted in the cloud. This tool backs the `/schedule` command. `trigger_id` is required for the `get`, `update`, `run`, and `list_runs` actions. `body` is required for `create`, `update`, and `create_webhook_trigger`, and optional for `run`. `create_webhook_trigger` attaches an event source to an existing routine, such as a [GitHub event](../02-Claude-Code-CLI/web-scheduled-tasks.md#add-a-github-trigger) that fires it. The `body` names the source, the events, and the routine to fire. Requires Claude Code v2.1.225 or later. `list_runs` lists a routine’s recent runs, and `get_run_log` reads one run’s log. `session_id` names the run to read, from a `list_runs` result, and `cursor` pages through either action’s results. Both actions require Claude Code v2.1.227 or later. This tool is available only when the session is authenticated with a claude.ai account on a plan with Routines enabled, and is absent when your organization’s policy disables [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md). On Claude Code v2.1.227 or later, the tool is also absent when an Owner has [turned off routines for the organization](../02-Claude-Code-CLI/web-scheduled-tasks.md#routines-are-disabled-by-your-organizations-policy). Before v2.1.227, a session with only the routines toggle turned off still showed the tool, and the server denied its calls.


[​](#pushnotification)

PushNotification

**Tool name:** `PushNotification`

```python
type PushNotificationInput = {
  message: string;
  status: "proactive";
};
```

Sends a proactive push notification to the user. Keep `message` under 200 characters because mobile operating systems truncate longer text. See the [PushNotification row in the tools reference](../02-Claude-Code-CLI/tools-reference.md) for provider availability; push delivery runs through Anthropic-hosted infrastructure that isn’t accessible from Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, or Microsoft Foundry.


[​](#repl)

REPL

Removed in v2.1.275. Through v2.1.274, an experimental `REPL` tool could be turned on with `CLAUDE_CODE_REPL=1` in the [`env` option](#options).


[​](#reportfindings)

ReportFindings

**Tool name:** `ReportFindings`

```python
type ReportFindingsInput = {
  level?: "low" | "medium" | "high" | "xhigh" | "max";
  findings: Array<{
    file: string;
    line?: number;
    summary: string;
    failure_scenario: string;
    short_summary?: string;
    category?: string;
    verdict?: "CONFIRMED" | "PLAUSIBLE";
    outcome?: "fixed" | "skipped" | "no_change_needed";
  }>;
};
```

Reports code-review findings as a structured list so Claude Code can render them instead of printing them as text. Findings are ordered most-severe first, with at most 32 per call, and the array is empty when none survived. Requires Claude Code v2.1.196 or later. `level` is optional and holds the effort level Claude reports for the review. Claude Code doesn’t compare it with the level the review ran at, so the two can differ. Each finding carries these fields:

- `file`: repo-relative path the finding is in. The optional `line` is the 1-indexed line it anchors to.
- `summary`: one-sentence statement of the defect. `failure_scenario` describes the concrete inputs and state that lead to the wrong output or crash.
- `short_summary`: optional compressed label of at most 60 characters for compact display. Requires Claude Code v2.1.212 or later.
- `category`: optional short kebab-case slug of the finding type, such as `correctness` or `test-coverage`. Requires Claude Code v2.1.199 or later.
- `verdict`: set when a verify pass ran; absent on inline-only reviews.
- `outcome`: set only when re-reporting after applying fixes.


[​](#artifact)

Artifact

**Tool name:** `Artifact`

```python
type ArtifactInput = {
  action?: "publish" | "list";
  file_path?: string;
  favicon?: string;
  icon?: string;
  limit?: number;
  scope?: "mine" | "shared" | "all";
  title?: string;
  description?: string;
  label?: string;
  url?: string;
  force?: boolean;
  capabilities?: Record<string, unknown>;
  contract?: "latest" | string;
};
```

Publishes a local `.html` or `.md` file as a hosted artifact page, or lists the user’s published artifacts. Omit `action` or pass `"publish"` to publish `file_path`, which is required for the publish action. Each field below applies to a publish:

- `icon`: one short generic word for the artifact’s browser-tab icon, such as `chart` or `map`. Claude includes it on a first publish and omits it on an update, which keeps the artifact’s stored icon.
- `favicon`: deprecated, and Claude omits it.
- `title`: names the published page in the browser tab and gallery when the HTML file has no `<title>` tag.
- `url`: targets an existing artifact to update in place instead of creating a new one.

`force` is a last-resort overwrite that discards a newer version another session published. On a conflict, the failed publish returns the newer content; Claude merges its changes onto that content, or re-reads the artifact, and publishes again. Pass `force` only when the user explicitly asks to discard that version. Pass `"list"` to enumerate the user’s published artifacts; only `limit` and `scope` may accompany it. `scope` defaults to `"mine"`, which lists artifacts the user owns; `"shared"` lists artifacts other people shared with the user, and `"all"` lists both. `limit` sets the most artifacts a listing returns, from 1 to 200. A `limit` above 50 requires Agent SDK v0.3.292 or later. Without `limit`, a listing returns up to 25.

- `capabilities`: the runtime capabilities the published page uses, keyed by capability name, such as the [connectors the page may call](../02-Claude-Code-CLI/artifacts.md#pull-live-data-with-mcp-connectors). The artifact service validates the declaration and rejects a publish that names a capability the account can’t use or gives one an invalid config. Pass `{}` to clear a stored declaration, and omit the field on a redeploy to keep it. Requires Agent SDK v0.3.235 or later.
- `contract`: the runtime version the published page runs against. Omit it to keep the artifact’s current version, pass `"latest"` to upgrade, or pass a specific version to pin or roll back. Requires Agent SDK v0.3.235 or later.

The types are exported, but the tool is off by default in Agent SDK sessions. Publishing also requires every condition in the [artifacts availability table](../02-Claude-Code-CLI/artifacts.md#availability), which sessions authenticated with an API key don’t meet.


[​](#projects)

Projects

**Tool name:** `Projects`

```python
type ProjectsInput = {
  method:
    | "project_info"
    | "project_read"
    | "project_search"
    | "project_write"
    | "project_delete";
  path?: string;
  content?: string;
  local_path?: string;
  present_to_user?: boolean;
  query?: string;
  n?: number;
};
```

Reads and writes the claude.ai Project attached to the session. Dispatches on `method`:

- `project_info`: returns project metadata and the doc list.
- `project_read`: reads one doc by `path`.
- `project_search`: queries the project’s knowledge base with `query`. `n` caps the hits and defaults to `5`.
- `project_write`: creates or replaces a doc at `path` from exactly one of `content`, which carries inline text, or `local_path`, which names a file inside the working directory. `present_to_user: true` marks the written doc as the deliverable the user needs to see.
- `project_delete`: deletes a doc by `path`.


[​](#readmcpresourcedir)

ReadMcpResourceDir

**Tool name:** `ReadMcpResourceDirTool`

```python
type ReadMcpResourceDirInput = {
  server: string;
  uri: string;
};
```

Lists the direct children of a directory resource on an MCP server. Only usable against a server that has declared support for directory listing; the listing isn’t recursive. Directory listing isn’t enabled in every session: when it’s off, the call returns an empty `resources` list and the `error` field reports that directory listing isn’t enabled.


[​](#refreshmcptools)

RefreshMcpTools

**Tool name:** `RefreshMcpTools`

```python
type RefreshMcpToolsInput = {
  server?: string; // refresh only this server; omit to refresh all connected servers
};
```

Re-queries the tool list of connected MCP servers and applies any changes. The types are exported, but Claude Code registers the tool only when you set `CLAUDE_CODE_ENABLE_REFRESH_MCP_TOOLS=1` in the [`env` option](#options), and only in sessions with at least one MCP server. Requires Claude Code v2.1.211 or later.


[​](#showonboardingrolepicker)

ShowOnboardingRolePicker

**Tool name:** `ShowOnboardingRolePicker`

```python
type ShowOnboardingRolePickerInput = {};
```

Renders a clickable role-picker chip row during Cowork onboarding so the user can pick their role and get a matching plugin installed. Takes no arguments; the role list is defined by the client. The call blocks until the user responds.


[​](#mcpinput)

McpInput

**Tool name:** dynamic MCP tool names of the form `mcp__<server>__<tool>`

```python
type McpInput = {
  [k: string]: unknown;
};
```

MCP tool arguments are an open object: each server defines its own parameters, so the type places no constraints on field names or values. Consult the server’s own tool schema for the fields a specific tool accepts.


[​](#tool-output-types)

Tool Output Types

Documentation of output schemas for all built-in Claude Code tools. These types are exported from `@anthropic-ai/claude-agent-sdk/sdk-tools` and represent the actual response data returned by each tool.


[​](#tooloutputschemas)

`ToolOutputSchemas`

Union of tool output types exported from `@anthropic-ai/claude-agent-sdk/sdk-tools`; members include:

```python
type ToolOutputSchemas =
  | AgentOutput
  | ArtifactOutput
  | AskUserQuestionOutput
  | BashOutput
  | CronCreateOutput
  | CronDeleteOutput
  | CronListOutput
  | EnterPlanModeOutput
  | EnterWorktreeOutput
  | ExitPlanModeOutput
  | ExitWorktreeOutput
  | FileEditOutput
  | FileReadOutput
  | FileWriteOutput
  | GlobOutput
  | GrepOutput
  | ListMcpResourcesOutput
  | McpOutput
  | MonitorOutput
  | NotebookEditOutput
  | ProjectsOutput
  | PushNotificationOutput
  | ReadMcpResourceDirOutput
  | ReadMcpResourceOutput
  | RefreshMcpToolsOutput
  | RemoteTriggerOutput
  | ReportFindingsOutput
  | ScheduleWakeupOutput
  | ShowOnboardingRolePickerOutput
  | TaskCreateOutput
  | TaskGetOutput
  | TaskListOutput
  | TaskStopOutput
  | TaskUpdateOutput
  | TodoWriteOutput
  | WebFetchOutput
  | WebSearchOutput
  | WorkflowOutput;
```


[​](#agent-2)

Agent

**Tool name:** `Agent`. The previous name `Task` is still accepted as an alias, and the `tools` array in the [`SDKSystemMessage`](#sdksystemmessage) init message currently lists this tool as `Task` for backward compatibility.

```python
type AgentOutput =
  | {
      status: "completed";
      agentId: string;
      agentType?: string;
      content: Array<{ type: "text"; text: string; citations?: unknown[] | null }>;
      resolvedModel?: string;
      modelsUsed?: string[];
      totalToolUseCount: number;
      totalDurationMs: number;
      totalTokens: number;
      usage: {
        input_tokens: number;
        output_tokens: number;
        cache_creation_input_tokens: number | null;
        cache_read_input_tokens: number | null;
        server_tool_use: {
          web_search_requests: number;
          web_fetch_requests: number;
        } | null;
        service_tier: string | null;
        cache_creation: {
          ephemeral_1h_input_tokens: number;
          ephemeral_5m_input_tokens: number;
        } | null;
        inference_geo?: string | null;
        speed?: string | null;
        iterations?: unknown;
        output_tokens_details?: {
          thinking_tokens?: number | null;
        } | null;
        fallback_credit?: unknown;
      };
      toolStats?: {
        readCount: number;
        searchCount: number;
        bashCount: number;
        editFileCount: number;
        linesAdded: number;
        linesRemoved: number;
        otherToolCount: number;
        frameCount?: number;
      };
      prompt: string;
      worktreePath?: string;
      worktreeBranch?: string;
    }
  | {
      status: "async_launched";
      isAsync?: true;
      agentId: string;
      description: string;
      resolvedModel?: string;
      modelsUsed?: string[];
      prompt: string;
      outputFile: string;
      canReadOutputFile?: boolean;
    }
  | {
      status: "remote_launched";
      taskId: string;
      sessionUrl: string;
      description: string;
      prompt: string;
      outputFile: string;
    };
```

Returns the result from the subagent. Discriminated on the `status` field: `"completed"` for finished tasks, `"async_launched"` for background tasks, and `"remote_launched"` for tasks Claude Code dispatched to a cloud session, where `sessionUrl` links to that session and `taskId` identifies it. On the `completed` variant, `resolvedModel` names the model the subagent started on, which can differ from the requested `model` input when [`availableModels`](../02-Claude-Code-CLI/model-config.md#restrict-model-selection) or another override applies. This field requires Claude Code v2.1.174 or later. On `async_launched`, it names the model in use when the task moved to the background. `modelsUsed` lists the models the subagent used, in order. The field is present only when a mid-run swap happened, and a model appears again when the run swapped back to it. On `async_launched`, the list covers the models used before backgrounding. Both `modelsUsed` and the backgrounding behavior of `resolvedModel` require Claude Code v2.1.212 or later. If Claude Code [kept the subagent’s isolated worktree](../02-Claude-Code-CLI/worktrees.md#isolate-subagents-with-worktrees), `worktreePath` on the `completed` result is where to find it. `worktreeBranch` is its branch, present when Claude Code created the worktree with git. Claude Code fills `usage` and `totalTokens` from the subagent’s final API request, not from the whole run, so `usage.service_tier` is the service tier string the API reported on that request. When present, `usage.output_tokens_details.thinking_tokens` is the number of that request’s output tokens that were thinking tokens. The `output_tokens_details` field requires TypeScript SDK v0.3.228 or later, which bundles Claude Code v2.1.228. The `fallback_credit` field requires TypeScript SDK v0.3.285 or later, which bundles Claude Code v2.1.285. `usage.output_tokens_details` matches [`Usage.output_tokens_details`](#usage) in meaning, scoped to that final request, but every level of it is optional here. Guard both the object and the field, for example `usage.output_tokens_details?.thinking_tokens ?? 0`, rather than reading it directly. Before v2.1.207, the published type was narrower. It omitted `worktreePath`, `worktreeBranch`, `citations`, `toolStats.frameCount`, and the `inference_geo`, `speed`, and `iterations` usage fields, and it typed `service_tier` as `"standard" | "priority" | "batch"`. Fields the type marks optional can be absent on results recorded by earlier versions.


[​](#askuserquestion-2)

AskUserQuestion

**Tool name:** `AskUserQuestion`

```python
type AskUserQuestionOutput = {
  questions: Array<{
    question: string;
    header: string;
    options: Array<{ label: string; description: string; preview?: string }>;
    multiSelect: boolean;
  }>;
  answers: Record<string, string>;
  response?: string;
  annotations?: Record<string, { preview?: string; notes?: string }>;
  afkTimeoutMs?: number;
};
```

Returns the questions asked and the user’s answers. `response` is set when the user typed a freeform reply instead of answering the structured questions; when present, Claude receives “The user responded: …” instead of the per-question answer list.


[​](#bash-2)

Bash

**Tool name:** `Bash`

```python
type BashOutput = {
  stdout: string;
  stderr: string;
  rawOutputPath?: string;
  interrupted: boolean;
  isImage?: boolean;
  backgroundTaskId?: string;
  backgroundedByUser?: boolean;
  timedOutAfterMs?: number;
  backgroundCwdHint?: string;
  backgroundEndsWithFinalResponse?: true;
  dangerouslyDisableSandbox?: boolean;
  returnCodeInterpretation?: string;
  noOutputExpected?: boolean;
  structuredContent?: unknown[];
  persistedOutputPath?: string;
  persistedOutputSize?: number;
  staleReadFileStateHint?: string;
  ghRateLimitHint?: string;
  gitOperation?: {
    commit?: { sha: string; kind: "committed" | "amended" | "cherry-picked"; branch?: string };
    push?: { branch: string };
    branch?: { ref: string; action: "merged" | "rebased" };
    pr?: {
      number: number;
      url?: string;
      action: "created" | "edited" | "merged" | "commented" | "closed" | "reopened" | "ready" | "draft" | "auto-merge-enabled" | "auto-merge-disabled";
    };
  };
};
```

The `stdout`, `stderr`, and `backgroundTaskId` fields carry:

| Field              | What it carries                                                                                 |
|--------------------|-------------------------------------------------------------------------------------------------|
| `stdout`           | The command’s stdout and stderr, merged into one interleaved stream                             |
| `stderr`           | Notices the tool itself adds, such as a shell working-directory reset, not the command’s stderr |
| `backgroundTaskId` | Present for background commands                                                                 |

`timedOutAfterMs` is the timeout in milliseconds, set when the command reached its timeout and moved to the background rather than starting there explicitly. `backgroundCwdHint` is set when the backgrounded command contained a directory-change builtin such as `cd`, `pushd`, `popd`, or `chdir`, and notes that the session working directory didn’t change. Both fields require Claude Code v2.1.210 or later. When a subagent running in the foreground owns a backgrounded command, the command [ends when that subagent’s run ends](../02-Claude-Code-CLI/tools-reference.md#when-a-background-command-stops). Claude Code sets `backgroundEndsWithFinalResponse` to `true` on such commands, and omits the field when the command survives the turn, as commands started by the main conversation or by background subagents do. The field requires Claude Code v2.1.227 or later. Claude Code sets `gitOperation.commit.branch` to the branch named in git’s commit summary line, and omits it for a commit made on a detached HEAD. The field requires Agent SDK v0.3.227 or later. Claude Code reports a `gh pr reopen` command as the `reopened` PR action, which requires Agent SDK v0.3.234 or later.


[​](#monitor-2)

Monitor

**Tool name:** `Monitor`

```python
type MonitorOutput = {
  taskId: string;
  timeoutMs: number;
  persistent?: boolean;
};
```

Returns the background task ID for the running monitor. Use this ID with `TaskStop` to cancel the watch early.


[​](#edit-2)

Edit

**Tool name:** `Edit`

```python
type FileEditOutput = {
  filePath: string;
  oldString: string;
  newString: string;
  originalFile: string | null;
  structuredPatch: Array<{
    oldStart: number;
    oldLines: number;
    newStart: number;
    newLines: number;
    lines: string[];
  }>;
  userModified: boolean;
  replaceAll: boolean;
  gitDiff?: {
    filename: string;
    status: "modified" | "added";
    additions: number;
    deletions: number;
    changes: number;
    patch: string;
    repository?: string | null;
  };
};
```

Returns the structured diff of the edit operation.


[​](#read-2)

Read

**Tool name:** `Read`

```python
type FileReadOutput =
  | {
      type: "text";
      file: {
        filePath: string;
        content: string;
        numLines: number;
        startLine: number;
        totalLines: number;
        /** True when a whole-file read was auto-paginated because it exceeded the token cap (the content is a partial first page). */
        truncatedByTokenCap?: boolean;
      };
    }
  | {
      type: "image";
      file: {
        base64: string;
        type: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
        originalSize: number;
        dimensions?: {
          originalWidth?: number;
          originalHeight?: number;
          displayWidth?: number;
          displayHeight?: number;
        };
      };
    }
  | {
      type: "notebook";
      file: {
        filePath: string;
        cells: unknown[];
      };
    }
  | {
      type: "pdf";
      file: {
        filePath: string;
        base64: string;
        originalSize: number;
      };
    }
  | {
      type: "parts";
      file: {
        filePath: string;
        originalSize: number;
        count: number;
        outputDir: string;
      };
      /** Document page number of the first extracted page; labels the page images in the tool_result content. */
      firstPage?: number;
      /** In-process only: the page-image bytes are delivered as image blocks in the tool_result content and aren't retained on the emitted tool_use_result, so this key is absent there. */
      pages?: {
        base64: string;
        mediaType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
        error?: string;
      }[];
    }
  | {
      type: "file_unchanged";
      file: {
        filePath: string;
      };
      /** Set when the dedup matched a startup-seeded entry (CLAUDE.md / nested memory) rather than a prior Read tool_result. */
      source?: "seeded";
    };
```

Returns file contents in a format appropriate to the file type. Discriminated on the `type` field.


[​](#write-2)

Write

**Tool name:** `Write`

```python
type FileWriteOutput = {
  type: "create" | "update";
  filePath: string;
  content: string;
  structuredPatch: Array<{
    oldStart: number;
    oldLines: number;
    newStart: number;
    newLines: number;
    lines: string[];
  }>;
  originalFile: string | null;
  gitDiff?: {
    filename: string;
    status: "modified" | "added";
    additions: number;
    deletions: number;
    changes: number;
    patch: string;
    repository?: string | null;
  };
  userModified?: boolean;
};
```

Returns the write result with structured diff information. What `originalFile` and `structuredPatch` hold depends on the write:

- For a newly created file, `originalFile` is null and `structuredPatch` is empty
- On an overwrite, `originalFile` carries the previous content, except when that content is larger than about 10 MB: Claude Code then skips the diff and returns `originalFile` null and `structuredPatch` empty
- `structuredPatch` is also empty when the write changed nothing or the diff timed out


[​](#glob-2)

Glob

**Tool name:** `Glob`

```python
type GlobOutput = {
  durationMs: number;
  numFiles: number;
  filenames: string[];
  truncated: boolean;
  totalMatches?: number;
  countIsComplete?: boolean;
};
```

Returns file paths matching the glob pattern, sorted by modification time. `totalMatches` and `countIsComplete` require Claude Code v2.1.191 or later. `totalMatches` reports the number of matching files before truncation. When `countIsComplete` is false, `totalMatches` is a lower bound because the underlying search truncated its own output.


[​](#grep-2)

Grep

**Tool name:** `Grep`

```python
type GrepOutput = {
  mode?: "content" | "files_with_matches" | "count";
  numFiles: number;
  filenames: string[];
  content?: string;
  numLines?: number;
  numMatches?: number;
  totalFiles?: number;
  totalLines?: number;
  appliedLimit?: number;
  appliedOffset?: number;
};
```

Returns search results. The shape varies by `mode`: file list, content with matches, or match counts. In `count` mode, `numFiles` and `numMatches` are totals over the full result set, not the paginated slice. Before v2.1.208, a `head_limit` or `offset` that truncated the listed entries also truncated those totals. `totalFiles` requires Claude Code v2.1.208 or later and reports the total number of results before `head_limit` and `offset` pagination in `files_with_matches` mode. `totalLines` requires Claude Code v2.1.210 or later and reports the total number of lines before pagination in `content` mode.


[​](#taskstop-2)

TaskStop

**Tool name:** `TaskStop`

```python
type TaskStopOutput = {
  message: string;
  task_id: string;
  task_type: string;
  command?: string;
};
```

Returns confirmation after stopping the background task.


[​](#notebookedit-2)

NotebookEdit

**Tool name:** `NotebookEdit`

```python
type NotebookEditOutput = {
  new_source: string;
  old_source?: string;
  cell_id?: string;
  cell_type: "code" | "markdown";
  language: string;
  edit_mode: string;
  error?: string;
  notebook_path: string;
  original_file: string;
  updated_file: string;
};
```

Returns the result of the notebook edit with original and updated file contents.


[​](#webfetch-2)

WebFetch

**Tool name:** `WebFetch`

```python
type WebFetchOutput = {
  bytes: number;
  code: number;
  codeText: string;
  result: string;
  durationMs: number;
  url: string;
  artifactRead?: {
    slug: string;
    ver?: string;
    seeded?: false;
  };
};
```

Returns the fetched content with HTTP status and metadata. `artifactRead` is Claude Code’s own record of an artifact read, present only when Claude fetched an artifact the session can publish to. Claude Code reads it back when a session resumes so a later publish builds on the right version; your code doesn’t need to act on it. `slug` names the artifact, `ver` is the version the read put on record and is absent when it recorded none, and `seeded: false` marks a read whose full source didn’t reach Claude. The `seeded` field requires Agent SDK v0.3.239 or later.


[​](#websearch-2)

WebSearch

**Tool name:** `WebSearch`

```python
type WebSearchOutput = {
  query: string;
  results: Array<
    | {
        tool_use_id: string;
        content: Array<{ title: string; url: string }>;
      }
    | string
  >;
  durationSeconds: number;
  searchCount?: number;
};
```

Returns search results from the web.


[​](#workflow-2)

Workflow

**Tool name:** `Workflow`

```python
type WorkflowOutput = {
  status: "async_launched" | "remote_launched";
  taskId: string;
  taskType?: "local_workflow" | "remote_agent";
  workflowName?: string;
  runId?: string;
  summary?: string;
  transcriptDir?: string;
  scriptPath?: string;
  sessionUrl?: string; // set when the workflow launched as a cloud session
  warning?: string;
  error?: string;
};
```

Returns immediately after the tool accepts the invocation. The final result arrives later as a task completion. Check `error` before treating the run as started: a script that fails its syntax check returns `status: "async_launched"` with `error` set, and never runs.

| Field           | Type                                   | Description                                                                                                                                                        |
|-----------------|----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `status`        | `"async_launched" | "remote_launched"` | The tool accepted the invocation. `"async_launched"` for in-process runs, `"remote_launched"` for runs dispatched to a cloud session instead of running in-process |
| `taskId`        | `string`                               | Background task identifier for the run                                                                                                                             |
| `taskType`      | `"local_workflow" | "remote_agent"`    | Task type of the registered background task, matching the `status` arm                                                                                             |
| `workflowName`  | `string`                               | The `meta.name` from the workflow script                                                                                                                           |
| `runId`         | `string`                               | Workflow run identifier to pass as `resumeFromRunId` on a later invocation. Absent for `remote_launched` runs, where the cloud session URL is the resume handle    |
| `summary`       | `string`                               | One-line description of what the workflow does                                                                                                                     |
| `transcriptDir` | `string`                               | Directory where subagent transcripts are written during execution                                                                                                  |
| `scriptPath`    | `string`                               | Path to the persisted workflow script for this run. Edit it and pass back as `scriptPath` to rerun without resending the script                                    |
| `sessionUrl`    | `string`                               | Cloud session URL, set when `status` is `"remote_launched"`                                                                                                        |
| `warning`       | `string`                               | Non-blocking heads-up, such as local git state diverging from the pushed branch a cloud session will clone                                                         |
| `error`         | `string`                               | Set when the script fails its syntax check. When present, the run did not start despite the launched status                                                        |


[​](#todowrite-2)

TodoWrite

**Tool name:** `TodoWrite`

```python
type TodoWriteOutput = {
  oldTodos: Array<{
    content: string;
    status: "pending" | "in_progress" | "completed";
    activeForm: string;
  }>;
  newTodos: Array<{
    content: string;
    status: "pending" | "in_progress" | "completed";
    activeForm: string;
  }>;
};
```

Returns the previous and updated task lists.

The following tools are available by default only on Claude 3.x models, Opus 4 through 4.7, Sonnet 4 through 4.6, and Haiku 4.5. On every other model, including model IDs Claude Code doesn’t recognize, they aren’t available unless you opt in:

- `TodoWrite`
- `TaskCreate`
- `TaskGet`
- `TaskUpdate`
- `TaskList`

Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.See [Model availability](agent-sdk-todo-tracking.md#model-availability) to opt in.


[​](#taskcreate-2)

TaskCreate

**Tool name:** `TaskCreate`

```python
type TaskCreateOutput = {
  task: {
    id: string;
    subject: string;
  };
};
```

Returns the created task with its assigned ID.


[​](#taskupdate-2)

TaskUpdate

**Tool name:** `TaskUpdate`

```python
type TaskUpdateOutput = {
  success: boolean;
  taskId: string;
  updatedFields: string[];
  error?: string;
  statusChange?: {
    from: string;
    to: string;
  };
};
```

Returns the update result, including which fields changed.


[​](#taskget-2)

TaskGet

**Tool name:** `TaskGet`

```python
type TaskGetOutput = {
  task: {
    id: string;
    subject: string;
    description: string;
    status: "pending" | "in_progress" | "completed";
    blocks: string[];
    blockedBy: string[];
  } | null;
};
```

Returns the full task record, or `null` when the ID is not found.


[​](#tasklist-2)

TaskList

**Tool name:** `TaskList`

```python
type TaskListOutput = {
  tasks: Array<{
    id: string;
    subject: string;
    status: "pending" | "in_progress" | "completed";
    owner?: string;
    blockedBy: string[];
  }>;
};
```

Returns a snapshot of all tasks in the current list.


[​](#exitplanmode-2)

ExitPlanMode

**Tool name:** `ExitPlanMode`

```python
type ExitPlanModeOutput = {
  plan: string | null;
  isAgent: boolean;
  filePath?: string;
  hasTaskTool?: boolean;
  planWasEdited?: boolean;
  awaitingLeaderApproval?: boolean;
  requestId?: string;
};
```

Returns the plan state after exiting plan mode.


[​](#listmcpresources-2)

ListMcpResources

**Tool name:** `ListMcpResourcesTool`

```python
type ListMcpResourcesOutput = Array<{
  uri: string;
  name: string;
  mimeType?: string;
  description?: string;
  server: string;
}>;
```

Returns an array of available MCP resources.


[​](#readmcpresource-2)

ReadMcpResource

**Tool name:** `ReadMcpResourceTool`

```python
type ReadMcpResourceOutput = {
  contents: Array<{
    uri: string;
    mimeType?: string;
    text?: string;
    blobSavedTo?: string;
  }>;
  error?: string;
};
```

Returns the contents of the requested MCP resource.


[​](#enterworktree-2)

EnterWorktree

**Tool name:** `EnterWorktree`

```python
type EnterWorktreeOutput = {
  worktreePath: string;
  worktreeBranch?: string;
  message: string;
};
```

Returns information about the git worktree.


[​](#exitworktree-2)

ExitWorktree

**Tool name:** `ExitWorktree`

```python
type ExitWorktreeOutput = {
  action: "keep" | "remove";
  originalCwd: string;
  worktreePath: string;
  worktreeBranch?: string;
  tmuxSessionName?: string;
  discardedFiles?: number;
  discardedCommits?: number;
  message: string;
};
```

Returns the action taken and details about the worktree that was exited.


[​](#enterplanmode-2)

EnterPlanMode

**Tool name:** `EnterPlanMode`

```python
type EnterPlanModeOutput = {
  message: string;
};
```

Returns a confirmation that plan mode was entered.


[​](#croncreate-2)

CronCreate

**Tool name:** `CronCreate`

```python
type CronCreateOutput = {
  id: string;
  humanSchedule: string;
  recurring: boolean;
  durable?: boolean; // true when persisted to .claude/scheduled_tasks.json; false when session-only
};
```

Returns the job ID and a human-readable description of the schedule.


[​](#crondelete-2)

CronDelete

**Tool name:** `CronDelete`

```python
type CronDeleteOutput = {
  id: string;
};
```

Returns the ID of the deleted job.


[​](#cronlist-2)

CronList

**Tool name:** `CronList`

```python
type CronListOutput = {
  jobs: {
    id: string;
    cron: string;
    humanSchedule: string;
    prompt: string;
    recurring?: boolean;
    durable?: boolean;
  }[];
};
```

Returns the scheduled cron jobs: durable jobs from `.claude/scheduled_tasks.json` and session-only jobs from the current session. A session-only job carries `durable: false`; jobs read from disk omit the field.


[​](#schedulewakeup-2)

ScheduleWakeup

**Tool name:** `ScheduleWakeup`

```python
type ScheduleWakeupOutput = {
  scheduledFor: number;
  clampedDelaySeconds: number;
  wasClamped: boolean;
  stopped?: boolean;
  cancelledWakeups?: number;
};
```

Returns when the wake-up will fire as an epoch millisecond timestamp, the delay actually used, and whether the requested delay was clamped. The `stopped` field is `true` when the call ended the loop with `stop: true`. It requires Claude Code v2.1.202 or later. The `cancelledWakeups` field counts how many pending wakeups a `stop: true` call cancelled. A value of 0 means nothing was pending, and a recurring `/loop` cron isn’t cancelled by `stop: true`. It requires Claude Code v2.1.206 or later.


[​](#remotetrigger-2)

RemoteTrigger

**Tool name:** `RemoteTrigger`

```python
type RemoteTriggerOutput = {
  status: number;
  json: string;
  summary?: string;
};
```

Returns the API response status and body for the trigger operation.


[​](#pushnotification-2)

PushNotification

**Tool name:** `PushNotification`

```python
type PushNotificationOutput = {
  message: string;
  pushSent?: boolean;
  localSent?: boolean;
  disabledReason?: "config_off" | "user_present" | "no_transport";
  sentAt?: string;
};
```

Returns delivery details, including whether a push or local notification was sent and why delivery was skipped.


[​](#reportfindings-2)

ReportFindings

**Tool name:** `ReportFindings`

```python
type ReportFindingsOutput = {
  count: number;
  level?: "low" | "medium" | "high" | "xhigh" | "max";
  findings: Array<{
    file: string;
    line?: number;
    summary: string;
    failure_scenario: string;
    short_summary?: string;
    category?: string;
    verdict?: "CONFIRMED" | "PLAUSIBLE";
    outcome?: "fixed" | "skipped" | "no_change_needed";
  }>;
};
```

Returns the number of findings reported, the `level` value Claude passed, and the findings echoed back for the result body. Requires Claude Code v2.1.196 or later. The echoed `short_summary` field requires Claude Code v2.1.212 or later.


[​](#artifact-2)

Artifact

**Tool name:** `Artifact`

```python
type ArtifactOutput =
  | {
      url: string;
      path: string;
      title?: string;
      version?: string;
      capabilities?: unknown;
      stored?: {
        contract: string;
        capabilities?: Record<string, unknown>;
      };
      warnings?: string[];
      contract?: string;
      updated?: boolean;
      liveSubscription?: string;
    }
  | {
      artifacts: Array<{
        title: string;
        url: string;
        updatedAt?: string;
        rel?: "mine" | "shared";
      }>;
      truncated?: boolean;
      total?: number;
      total_at_least?: true;
      scope?: "shared" | "all";
    };
```

Returns the published page’s `url` and the local `path` that was published for the publish action, with `updated` set to true when the publish redeployed an existing artifact, and `warnings` carrying any publish-time advisories. The list action returns the `artifacts` rows instead, with `truncated` set when more artifacts exist than the requested limit. On listings whose scope isn’t `"mine"`, each row carries `rel` marking whether the user owns the artifact or it was shared with them, and the output’s `scope` records which non-default scope produced the listing; both are absent on default listings. A list result also reports `total`, the number of artifacts that match the listed scope, including ones beyond `limit`. When `total_at_least` is set, that number is a lower bound and more artifacts may exist. Both fields require Agent SDK v0.3.292 or later.


[​](#projects-2)

Projects

**Tool name:** `Projects`

```python
type ProjectsOutput =
  | {
      method: "project_info";
      notice?: string;
      name: string;
      description: string;
      instructions: string;
      docs: Array<{ path: string; created_at: string | null }>;
      files?: Array<{
        path: string;
        file_kind: string;
        created_at: string | null;
      }>;
      sync_sources?: Array<{
        type: string | null;
        config: Record<string, unknown>;
      }>;
      knowledge: {
        knowledge_size: number;
        max_knowledge_size: number;
      };
    }
  | {
      method: "project_read";
      notice?: string;
      path: string;
      file_kind?: string;
      content?: string;
      local_file?: string;
      created_at: string | null;
    }
  | {
      method: "project_search";
      notice?: string;
      rag: boolean;
      hits?: Array<{ name?: string; doc_uuid?: string; text?: string }>;
      docs?: string[];
    }
  | {
      method: "project_write";
      notice?: string;
      path: string;
      doc_uuid: string;
      replaced: boolean;
      present_to_user?: boolean;
      local_path?: string;
    }
  | {
      method: "project_delete";
      notice?: string;
      path: string;
      deleted: boolean;
    };
```

Discriminated on the `method` field, mirroring the input. `project_read` returns small text docs inline in `content` and writes larger docs to a `local_file` path instead; `project_search` returns RAG `hits` with `rag: true` when the project’s index is available and falls back to a `docs` path list otherwise.


[​](#readmcpresourcedir-2)

ReadMcpResourceDir

**Tool name:** `ReadMcpResourceDirTool`

```python
type ReadMcpResourceDirOutput = {
  resources: Array<{
    uri: string;
    name: string;
    mimeType?: string;
  }>;
  error?: string;
};
```

Returns the direct children of the directory resource. Subdirectories appear with mimeType `"inode/directory"`; `error` carries a human-readable message when the server couldn’t list the directory.


[​](#refreshmcptools-2)

RefreshMcpTools

**Tool name:** `RefreshMcpTools`

```python
type RefreshMcpToolsOutput = Array<{
  server: string;
  status: "refreshed" | "error" | "not_connected";
  toolCount?: number; // tools now available from this server
  added?: string[]; // tool names this refresh added
  removed?: string[]; // tool names this refresh removed
  error?: string; // why the refresh failed or the server was unavailable
}>;
```

Returns one entry per server: `refreshed` means the re-queried tool list was applied, `error` means the re-query failed and the previous tool set was kept, and `not_connected` means the server has no live connection to query.


[​](#showonboardingrolepicker-2)

ShowOnboardingRolePicker

**Tool name:** `ShowOnboardingRolePicker`

```python
type ShowOnboardingRolePickerOutput = {
  role?: string;
  dismissed?: boolean;
};
```

Returns the user’s selection: `role` when they picked a role chip or typed one, and `dismissed: true` when they closed the picker. An empty object means the user approved the call without picking a role.


[​](#mcpoutput)

McpOutput

**Tool name:** dynamic MCP tool names of the form `mcp__<server>__<tool>`

```python
type McpOutput =
  | string
  | {
      type: string;
      [k: string]: unknown;
    }[]
  | {
      [k: string]: unknown;
    };
```

MCP tool results are returned as a string or an array of content blocks, depending on the server. The trailing plain-object branch in the exported type is a schema-generation artifact. For a result that also carries `structuredContent` or resource links, see [`tool_use_result`](#sdkusermessage), which holds this value in its `content` member. At runtime the value may also be `undefined`, although the exported type doesn’t model this.


[​](#permission-types)

Permission Types


[​](#permissionupdate)

`PermissionUpdate`

Operations for updating permissions.

```python
type PermissionUpdate =
  | {
      type: "addRules";
      rules: PermissionRuleValue[];
      behavior: PermissionBehavior;
      destination: PermissionUpdateDestination;
    }
  | {
      type: "replaceRules";
      rules: PermissionRuleValue[];
      behavior: PermissionBehavior;
      destination: PermissionUpdateDestination;
    }
  | {
      type: "removeRules";
      rules: PermissionRuleValue[];
      behavior: PermissionBehavior;
      destination: PermissionUpdateDestination;
    }
  | {
      type: "setMode";
      mode: PermissionMode;
      destination: PermissionUpdateDestination;
    }
  | {
      type: "addDirectories";
      directories: string[];
      destination: PermissionUpdateDestination;
    }
  | {
      type: "removeDirectories";
      directories: string[];
      destination: PermissionUpdateDestination;
    };
```


[​](#permissionbehavior)

`PermissionBehavior`

```python
type PermissionBehavior = "allow" | "deny" | "ask";
```


[​](#permissionupdatedestination)

`PermissionUpdateDestination`

```python
type PermissionUpdateDestination =
  | "userSettings" // Global user settings
  | "projectSettings" // Per-directory project settings
  | "localSettings" // Local project settings
  | "session" // Current session only
  | "cliArg"; // CLI argument
```


[​](#permissionrulevalue)

`PermissionRuleValue`

```python
type PermissionRuleValue = {
  toolName: string;
  ruleContent?: string;
};
```


[​](#other-types)

Other Types


[​](#apikeysource)

`ApiKeySource`

Where the API key for the session’s requests came from, reported as `apiKeySource` on the [`SDKSystemMessage`](#sdksystemmessage) init message.

```python
type ApiKeySource =
  | "ANTHROPIC_API_KEY"
  | "apiKeyHelper"
  | "/login managed key"
  | "none"
  | "user"
  | "project"
  | "org"
  | "temporary"
  | "oauth";
```

Claude Code reports one of four values:

| Value                | Key in use                                                                                                                           |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `ANTHROPIC_API_KEY`  | The key in the `ANTHROPIC_API_KEY` environment variable                                                                              |
| `apiKeyHelper`       | The key returned by your [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) command                                          |
| `/login managed key` | The key Claude Code stored when you logged in with a [Claude Console account](../02-Claude-Code-CLI/authentication.md#claude-console-authentication) |
| `none`               | No API key. The session authenticates another way, such as a claude.ai login, a bearer token, or a cloud provider                    |

Agent SDK v0.3.234 and later list these four values in the type. The type also keeps `user`, `project`, `org`, `temporary`, and `oauth` so older code still compiles, and Claude Code doesn’t report them.


[​](#sdkbeta)

`SdkBeta`

Available beta features that can be enabled via the `betas` option. See [Beta headers](../04-API-Reference/Endpoints/beta-headers.md) for more information.

```python
type SdkBeta = "context-1m-2025-08-07";
```

On the Claude API, the `context-1m-2025-08-07` beta is retired for Claude Sonnet 4.5 and Claude Sonnet 4. If you still pass it with either model, requests that exceed the standard 200K-token context window return an error, so remove it from `betas`. To run a session with a 1M-token context window, set `model` to a model that [runs with the 1M window by default](../02-Claude-Code-CLI/model-config.md#extended-context), such as `claude-sonnet-5-5` or `claude-opus-5-5`. For a model that reaches 1M only through its `[1m]` variant, append the suffix to the model ID, as in `claude-opus-4-6[1m]`.


[​](#slashcommand)

`SlashCommand`

Information about an available command.

```python
type SlashCommand = {
  name: string;
  description: string;
  argumentHint: string;
  aliases?: string[];
  builtin?: boolean;
};
```

`builtin` is `true` on a row when the command is Claude Code’s own and typing `/name` runs it. It’s absent for a command defined by a user, project, plugin, or MCP server, and for a bundled command that one of those [replaces by name](../08-Plugins-Skills/skills.md#resolve-skills-that-share-a-name). Requires Agent SDK v0.3.277 or later.


[​](#modelinfo)

`ModelInfo`

Information about an available model.

```python
type ModelInfo = {
  value: string;
  resolvedModel?: string;
  displayName: string;
  description: string;
  supportsEffort?: boolean;
  supportedEffortLevels?: ("low" | "medium" | "high" | "xhigh" | "max")[];
  supportsAdaptiveThinking?: boolean;
  supportsFastMode?: boolean;
  supportsAutoMode?: boolean;
};
```

| Field                      | Type                                                          | Description                                                                                                                                           |
|:---------------------------|:--------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------|
| `value`                    | `string`                                                      | Model identifier to pass in API calls                                                                                                                 |
| `resolvedModel`            | `string | undefined`                                          | The model ID that this entry’s `value` resolves to, such as `claude-sonnet-5-5` for the `sonnet` alias entry. Requires Claude Code v2.1.197 or later. |
| `displayName`              | `string`                                                      | Human-readable display name                                                                                                                           |
| `description`              | `string`                                                      | Description of the model’s capabilities                                                                                                               |
| `supportsEffort`           | `boolean | undefined`                                         | Whether this model supports effort levels                                                                                                             |
| `supportedEffortLevels`    | `("low" | "medium" | "high" | "xhigh" | "max")[] | undefined` | Effort levels this model accepts                                                                                                                      |
| `supportsAdaptiveThinking` | `boolean | undefined`                                         | Whether this model supports adaptive thinking, where Claude decides when and how much to think                                                        |
| `supportsFastMode`         | `boolean | undefined`                                         | Whether this model supports fast mode                                                                                                                 |
| `supportsAutoMode`         | `boolean | undefined`                                         | Whether this model supports auto mode                                                                                                                 |


[​](#agentinfo)

`AgentInfo`

Information about an available subagent that can be invoked via the Agent tool.

```python
type AgentInfo = {
  name: string;
  description: string;
  model?: string;
};
```

| Field         | Type                 | Description                                                                                                                                                                                              |
|:--------------|:---------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name`        | `string`             | Agent type identifier (for example, `"Explore"`, `"general-purpose"`)                                                                                                                                    |
| `description` | `string`             | Description of when to use this agent                                                                                                                                                                    |
| `model`       | `string | undefined` | Model this agent uses: an alias or model ID, or `'inherit'` for the parent’s model. When it’s `undefined`, Claude Code picks the model in the [subagent model order](../09-Agents-Patterns/sub-agents.md#choose-a-model) |


[​](#mcpserverprovenance)

`McpServerProvenance`

The MCP server that serves an `mcp__*` tool, and where that server’s definition came from. The [`PreToolUse`](#pretoolusehookinput), `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied` hook inputs carry it as `mcp_server`, and the [`CanUseTool`](#canusetool) options carry it as `mcpServer`. Both omit it for tools that don’t come from an MCP server.

```python
type McpServerProvenance = {
  name: string;
  source: string;
};
```

| Field    | Type     | Description                                                                                                 |
|:---------|:---------|:------------------------------------------------------------------------------------------------------------|
| `name`   | `string` | The name the server is registered under, the same value [`mcpServerStatus()`](#query-object) reports for it |
| `source` | `string` | Where the server’s definition came from: `sdk`, `plugin`, or a configuration scope                          |

`source` takes one of the following values. The set is open, so treat a value you don’t recognize as a configured source, never as `sdk`:

- **`sdk`**: an in-process server your application registered. Only the SDK host application can register one, so a configured server never reports `sdk`, whatever its name.
- **`plugin`**: a server a [plugin](agent-sdk-plugins.md) provides. Its `name` is the scoped `plugin:<plugin-name>:<server-name>` form described under [plugin-provided MCP servers](../06-MCP-Tools/General/mcp.md#plugin-provided-mcp-servers).
- **A configuration scope**: `user`, `project`, `local`, `dynamic`, `managed`, `enterprise`, `claudeai`, or `agent`. A `.mcp.json` server reports `project`, and [MCP installation scopes](../06-MCP-Tools/General/mcp.md#mcp-installation-scopes) defines `local`, `project`, and `user`. Servers your application passes in the [`mcpServers` option](#options), other than in-process SDK servers, report `dynamic`.

Base trust decisions on `source`, not on `name` or the `mcp__<server>__` tool-name prefix. For any source other than `sdk`, `name` is untrusted text: escape it before display. `McpServerProvenance` and the fields that carry it require Agent SDK v0.3.274 or later.


[​](#mcpserverstatus)

`McpServerStatus`

Status of a connected MCP server.

```python
type McpServerStatus = {
  name: string;
  status: "connected" | "failed" | "needs-auth" | "pending" | "disabled";
  serverInfo?: {
    name: string;
    version: string;
  };
  error?: string;
  config?: McpServerStatusConfig;
  scope?: string;
  source?: string;
  tools?: {
    name: string;
    description?: string;
    annotations?: {
      readOnly?: boolean;
      destructive?: boolean;
      openWorld?: boolean;
    };
    _meta?: Record<string, unknown>;
  }[];
};
```

`source` says where the server’s definition came from, with the same values and trust rule as [`McpServerProvenance`](#mcpserverprovenance)’s `source`. The field requires Agent SDK v0.3.274 or later and is absent on earlier versions. `_meta` on a `tools` entry carries the MCP Apps members of that tool’s `_meta`, so your application can find the `ui://` resource to render with [`readMcpResource()`](#query-object). Claude Code passes through the `ui` object and the deprecated flat `ui/resourceUri` string, and withholds every other key. Inside `ui`, `resourceUri` is a `ui://` string and `visibility` an array of `"model"` and `"app"` when the server sets them, and any other member passes through unchanged. Claude Code drops either key when the value is malformed, and omits `_meta` from a tool that declares neither. The field is present only when the init message’s [`capabilities`](#sdksystemmessage) include `mcp_tool_ui_meta_v1`, and requires TypeScript Agent SDK v0.3.280 or later.


[​](#mcpserverstatusconfig)

`McpServerStatusConfig`

The configuration of an MCP server as reported by `mcpServerStatus()`. This is the union of all MCP server transport types.

```python
type McpServerStatusConfig =
  | McpStdioServerConfig
  | McpSSEServerConfig
  | McpHttpServerConfig
  | McpSdkServerConfig
  | McpClaudeAIProxyServerConfig;
```

See [`McpServerConfig`](#mcpserverconfig) for details on each transport type.


[​](#accountinfo)

`AccountInfo`

Account information for the authenticated user.

```python
type AccountInfo = {
  email?: string;
  organization?: string;
  subscriptionType?: string;
  tokenSource?: string;
  apiKeySource?: string;
};
```


[​](#modelusage)

`ModelUsage`

Per-model usage statistics returned in result messages. The `costUSD` value is a client-side estimate. See [Track cost and usage](agent-sdk-cost-tracking.md) for billing caveats.

```python
type ModelUsage = {
  inputTokens: number;
  outputTokens: number;
  thinkingTokens?: number;
  cacheReadInputTokens: number;
  cacheCreationInputTokens: number;
  webSearchRequests: number;
  costUSD: number;
  contextWindow: number;
  maxOutputTokens: number;
  canonicalModel?: string;
  provider?: string;
  costBasis?: 'list' | 'managed' | 'unknown';
};
```

`thinkingTokens` counts the thinking tokens this model generated. `outputTokens` already includes them, so don’t add the two together. The field is absent until a turn runs on a Claude Code version that records it, so a resumed session that began on an earlier version reports a partial count. `thinkingTokens` requires Agent SDK v0.3.257 or later. The `canonicalModel` and `provider` fields require Claude Code v2.1.218 or later. `canonicalModel` is the canonical model ID that the pricing lookup uses; it can differ from the raw model string that keys the entry, for example when that string is a provider-specific ID or an alias. `provider` names the API backend that served the model, such as `firstParty`, `bedrock`, `vertex`, `foundry`, `anthropicAws`, `mantle`, or `gateway`. `costBasis` names the price table that priced the model’s latest request: `list` for list price, `managed` for a [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) table, or `unknown` when neither matched the model ID. The field requires Claude Code v2.1.246 or later.


[​](#configscope)

`ConfigScope`

```python
type ConfigScope = "local" | "user" | "project";
```


[​](#nonnullableusage)

`NonNullableUsage`

A version of [`Usage`](#usage) with every nullable field made non-nullable except `fallback_credit`, which can still be `null`.

```python
type NonNullableUsage = {
  [K in keyof Usage]: K extends "fallback_credit"
    ? Usage[K]
    : NonNullable<Usage[K]>;
};
```


[​](#usage)

`Usage`

Token usage statistics. This is the `BetaUsage` type from `@anthropic-ai/sdk`.

```python
type Usage = {
  input_tokens: number;
  output_tokens: number;
  cache_creation_input_tokens: number | null;
  cache_read_input_tokens: number | null;
  cache_creation: {
    ephemeral_5m_input_tokens: number;
    ephemeral_1h_input_tokens: number;
  } | null;
  server_tool_use: BetaServerToolUsage | null;
  service_tier: "standard" | "priority" | "batch" | null;
  speed: "standard" | "fast" | null;
  inference_geo: string | null;
  iterations: BetaIterationsUsage | null;
  output_tokens_details: BetaOutputTokensDetails | null;
  fallback_credit: BetaFallbackCreditUsage | null;
};
```

`BetaServerToolUsage`, `BetaIterationsUsage`, `BetaOutputTokensDetails`, and `BetaFallbackCreditUsage` are defined in `@anthropic-ai/sdk`. `output_tokens_details` breaks the billed output down by category. It currently carries one field, `thinking_tokens: number`, counting the output tokens the model generated as internal reasoning, including the thinking-block delimiters. The `output_tokens_details` field requires TypeScript SDK v0.3.228 or later, which bundles Claude Code v2.1.228.

- **Billing**: read the breakdown for observability, not for billing. `output_tokens` stays the authoritative total, and `output_tokens - thinking_tokens` approximates the non-reasoning output.
- **What the count covers**: the raw reasoning the model produced, which can be longer than the thinking text returned in the response body. The API computes it by re-tokenizing that raw text, so it can differ from the model’s exact generation count by a few tokens.
- **Streaming**: on streamed assistant messages this breakdown, like `output_tokens`, is a `message_start` placeholder and carries no real count, so read it from the result message’s `usage` as [Read output tokens from the result message](agent-sdk-cost-tracking.md#read-output-tokens-from-the-result-message) describes. On the result message, `thinking_tokens` reads `0` when the model or provider reports no breakdown.
- **`null` cases**: `output_tokens_details` itself is `null` on assistant messages Claude Code synthesizes, such as API-error messages.

Whether `Usage` carries `fallback_credit` depends on your installed `@anthropic-ai/sdk`, which added it in 0.115.0.


[​](#calltoolresult)

`CallToolResult`

MCP tool result type (from `@modelcontextprotocol/sdk/types.js`). `structuredContent` is a JSON object that can be returned alongside `content`, including image blocks. See [Return structured data](agent-sdk-custom-tools.md#return-structured-data).

```python
type CallToolResult = {
  content: Array<{
    type: "text" | "image" | "audio" | "resource" | "resource_link";
    // Additional fields vary by type
  }>;
  structuredContent?: Record<string, unknown>;
  isError?: boolean;
};
```


[​](#sdkmcpresourcelink)

`SDKMcpResourceLink`

One file an MCP tool returned by reference. Claude Code builds each entry from a `resource_link` block in the tool’s result and delivers the list as `resourceLinks` on [`SDKUserMessage.tool_use_result`](#sdkusermessage), or as `resource_links` on [`SDKTaskNotificationMessage`](#sdktasknotificationmessage) when the call finished in the background. Requires Agent SDK v0.3.257 or later.

```python
type SDKMcpResourceLink = {
  uri: string;
  name: string;
  title?: string;
  description?: string;
  mimeType?: string;
  size?: number;
  annotations?: Record<string, unknown>;
};
```

Claude Code drops a block whose `uri` or `name` isn’t a string, and leaves out an optional field whose value isn’t of the listed type.

| Field         | Type                                  | Description                                                 |
|:--------------|:--------------------------------------|:------------------------------------------------------------|
| `uri`         | `string`                              | URI of the resource, as the server returned it              |
| `name`        | `string`                              | Name the server gave the resource                           |
| `title`       | `string | undefined`                  | Display title, when the server set one                      |
| `description` | `string | undefined`                  | Description, when the server set one                        |
| `mimeType`    | `string | undefined`                  | MIME type, when the server set one                          |
| `size`        | `number | undefined`                  | Size in bytes, when the server set one                      |
| `annotations` | `Record<string, unknown> | undefined` | The block’s MCP annotations object, when the server set one |


[​](#thinkingconfig)

`ThinkingConfig`

Controls Claude’s thinking/reasoning behavior. Takes precedence over the deprecated `maxThinkingTokens`.

```python
type ThinkingDisplay = "summarized" | "omitted";

type ThinkingConfig =
  | { type: "adaptive"; display?: ThinkingDisplay } // The model determines when and how much to reason (Opus 4.6+)
  | { type: "enabled"; budgetTokens?: number; display?: ThinkingDisplay } // Fixed thinking token budget
  | { type: "disabled" }; // No extended thinking
```

The optional `display` field controls whether thinking text is returned `"summarized"` or `"omitted"`. On Claude Opus 4.7 and later, the API default is `"omitted"`, so set `"summarized"` to receive thinking content in `thinking` blocks. Claude Code doesn’t pass your `display` value to some providers, such as Amazon Bedrock and Google Cloud’s Agent Platform. On those providers, Opus 4.7 and later return empty `thinking` blocks even when you set `display` to `"summarized"`.


[​](#spawnedprocess)

`SpawnedProcess`

Interface for custom process spawning (used with `spawnClaudeCodeProcess` option). `ChildProcess` already satisfies this interface.

```python
interface SpawnedProcess {
  stdin: Writable;
  stdout: Readable;
  readonly killed: boolean;
  readonly exitCode: number | null;
  kill(signal: NodeJS.Signals): boolean;
  on(
    event: "exit",
    listener: (code: number | null, signal: NodeJS.Signals | null) => void
  ): void;
  on(event: "error", listener: (error: Error) => void): void;
  once(
    event: "exit",
    listener: (code: number | null, signal: NodeJS.Signals | null) => void
  ): void;
  once(event: "error", listener: (error: Error) => void): void;
  off(
    event: "exit",
    listener: (code: number | null, signal: NodeJS.Signals | null) => void
  ): void;
  off(event: "error", listener: (error: Error) => void): void;
}
```


[​](#spawnoptions)

`SpawnOptions`

Options passed to the custom spawn function.

```python
interface SpawnOptions {
  command: string;
  args: string[];
  cwd?: string;
  env: Record<string, string | undefined>;
  signal: AbortSignal;
}
```

The `signal` field tells your spawn function when to tear down the process. Pass it as the `signal` option to Node’s `spawn()`, or pass it to your VM or container teardown handler.This signal does not fire the instant [`Options.abortController`](#options) aborts. The SDK first closes the process’s stdin and waits about two seconds so the CLI can shut down cleanly, then aborts this signal. To react the moment the caller aborts instead, listen on your own `Options.abortController.signal`, which your spawn function can reference from its enclosing scope.


[​](#mcpsetserversresult)

`McpSetServersResult`

Result of a `setMcpServers()` operation.

```python
type McpSetServersResult = {
  added: string[];
  removed: string[];
  errors: Record<string, string>;
};
```

When you call `setMcpServers()`, Claude Code applies these rules:

- **Servers the call doesn’t name**: outside a [cloud session](../02-Claude-Code-CLI/claude-code-on-the-web.md), Claude Code disconnects the servers an earlier `setMcpServers()` call added and the in-process SDK servers, and lists them in `removed`. Other servers keep running and aren’t listed in `removed`, among them the stdio, HTTP, and SSE servers from the [`mcpServers`](#options) option, servers from settings files, and plugin-provided servers.
- **Servers the call names**: Claude Code replaces a stdio, HTTP, or SSE server that an earlier `setMcpServers()` call added only when its config differs from the one you passed. An in-process SDK server already registered under that name stays as it is, so to swap one, leave it out of one call and add it in the next.
- **Built-in servers the CLI started at startup**: if the call names one, Claude Code drops that entry and reports it in `errors`.

The promise resolves after newly added stdio, HTTP, and SSE servers connect or fail, so tools from servers that connected are available on the next turn. `added` lists the servers Claude Code added or replaced, whether or not they connected. A server that failed to connect appears in both `added` and `errors`, with the failure text under `errors` and a `failed` row in [`mcpServerStatus()`](#methods). Before Claude Code v2.1.257, a server whose connection attempt threw was reported only under `errors`.


[​](#rewindfilesresult)

`RewindFilesResult`

Result of a `rewindFiles()` operation.

```python
type RewindFilesResult = {
  canRewind: boolean;
  error?: string;
  filesChanged?: string[];
  insertions?: number;
  deletions?: number;
  skippedLinks?: number;
};
```

`skippedLinks` counts the tracked paths the rewind refused to restore or delete for link safety: a symlink, hard link, or other non-regular file at the tracked path, a parent directory that no longer resolves to where it pointed when the checkpoint was taken, or a backup that couldn’t be read safely. The field requires Claude Code v2.1.216 or later. A preview call with `rewindFiles(userMessageId, { dryRun: true })` never sets it.


[​](#sdkstatusmessage)

`SDKStatusMessage`

Status update message (for example, compacting).

```python
type SDKStatusMessage = {
  type: "system";
  subtype: "status";
  status: "compacting" | null;
  permissionMode?: PermissionMode;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdktasknotificationmessage)

`SDKTaskNotificationMessage`

Notification when a background task completes, fails, or is stopped. Background tasks include `run_in_background` Bash commands, [Monitor](#monitor) watches, and background subagents. For the `ambient` field, see [`SDKTaskStartedMessage`](#sdktaskstartedmessage), which defines it and its version requirement.

```python
type SDKTaskNotificationMessage = {
  type: "system";
  subtype: "task_notification";
  task_id: string;
  tool_use_id?: string;
  status: "completed" | "failed" | "stopped";
  reason?: "worker_restart";
  output_file: string;
  summary: string;
  ambient?: boolean;
  usage?: {
    total_tokens: number;
    tool_uses: number;
    duration_ms: number;
  };
  resource_links?: SDKMcpResourceLink[];
  uuid: UUID;
  session_id: string;
};
```

`reason` is set when a task ends for a cause other than its own completion, failure, or stop, and requires Agent SDK v0.3.273 or later. Claude Code sets it only in sessions that connect through claude.ai: cloud sessions, including those on self-hosted runners, and Remote Control sessions. A local `query()` call never sets it. Its one value, `worker_restart`, means the Claude Code process that was running the task restarted. The notification carries status `"stopped"`, so treat the task as neither completed nor failed. When Claude Code [moves a long MCP tool call to the background](../06-MCP-Tools/General/mcp.md#automatic-backgrounding-of-long-tool-calls), the `tool_result` block for that call holds only a placeholder and the call’s real result arrives in this notification. Match the notification to the call with `tool_use_id`. On a `completed` notification, `resource_links` lists the files the tool returned by reference as [`SDKMcpResourceLink`](#sdkmcpresourcelink) entries, with the same 50-link and 64 KiB limits as [`tool_use_result.resourceLinks`](#sdkusermessage). Claude Code omits `resource_links` when the result had no links and on notifications for tasks that aren’t MCP tool calls. `resource_links` requires Agent SDK v0.3.257 or later. Claude Code prepends a notice to every task notification it sends to the model, except deliveries stamped with the [`scheduled-trigger` subkind](#task-notification-subkinds), which carry an assigned-task framing instead. The notice states that no human input has occurred, so the model doesn’t treat the notification as a user instruction or approval. To detect a task-notification turn, check `origin.kind === "task-notification"` on the [`SDKUserMessage`](#sdkusermessage) or [`SDKResultMessage`](#sdkresultmessage) rather than matching on the notice text. Read `subkind` from the same field if you need to know what raised it. Before v2.1.205, Claude Code left the notice off notifications that arrived while the session was idle.


[​](#sdktoolusesummarymessage)

`SDKToolUseSummaryMessage`

Summary of tool usage in a conversation.

```python
type SDKToolUseSummaryMessage = {
  type: "tool_use_summary";
  summary: string;
  preceding_tool_use_ids: string[];
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkhookstartedmessage)

`SDKHookStartedMessage`

Emitted when a hook begins executing. Claude Code delivers this message, [`SDKHookProgressMessage`](#sdkhookprogressmessage), and [`SDKHookResponseMessage`](#sdkhookresponsemessage) to the message stream immediately, including while a `SessionStart` or `Setup` hook is still running during session startup. Claude Code v2.1.169 through v2.1.203 delivered these messages in one batch after a `SessionStart` or `Setup` hook completed; v2.1.204 restored live delivery.

```python
type SDKHookStartedMessage = {
  type: "system";
  subtype: "hook_started";
  hook_id: string;
  hook_name: string;
  hook_event: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkhookprogressmessage)

`SDKHookProgressMessage`

Emitted while a hook is running, with stdout/stderr output.

```python
type SDKHookProgressMessage = {
  type: "system";
  subtype: "hook_progress";
  hook_id: string;
  hook_name: string;
  hook_event: string;
  stdout: string;
  stderr: string;
  output: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkhookresponsemessage)

`SDKHookResponseMessage`

Emitted when a hook finishes executing.

```python
type SDKHookResponseMessage = {
  type: "system";
  subtype: "hook_response";
  hook_id: string;
  hook_name: string;
  hook_event: string;
  output: string;
  stdout: string;
  stderr: string;
  exit_code?: number;
  outcome: "success" | "error" | "cancelled";
  uuid: UUID;
  session_id: string;
};
```


[​](#sdktoolprogressmessage)

`SDKToolProgressMessage`

Emitted periodically while a tool is executing to indicate progress.

```python
type SDKToolProgressMessage = {
  type: "tool_progress";
  tool_use_id: string;
  tool_name: string;
  parent_tool_use_id: string | null;
  elapsed_time_seconds: number;
  task_id?: string;
  heartbeat?: boolean;
  subagent_type?: string;
  subagent_retry?: {
    agent_id: string;
    attempt: number;
    max_retries: number;
    retry_delay_ms: number;
    error_status: number | null;
    error_category: string;
  };
  uuid: UUID;
  session_id: string;
};
```

While a tool call runs in the main conversation, Claude Code emits a `tool_progress` message every 30 seconds with `heartbeat: true`. Each heartbeat carries the tool name and elapsed seconds, so you can distinguish a long-running call from a stalled session. Claude Code doesn’t emit heartbeats for tool calls inside a subagent. The `heartbeat` field requires Agent SDK v0.3.214 or later. Before v2.1.257, Claude Code didn’t emit heartbeats for a foreground Agent tool call either. On `tool_progress` messages for the Agent tool other than heartbeats, `subagent_type` names the running subagent type, such as `general-purpose`. `subagent_retry` is present while that subagent waits out an API error backoff, such as a rate limit or overload, with one message per retry attempt. Both fields require Agent SDK v0.3.214 or later. To render a retry indicator from `subagent_retry`:

- Track the indicator by `parent_tool_use_id`, which is unique per subagent. `tool_use_id` is shared by parallel subagents from one assistant turn, so tracking by it would let one subagent’s update clear another’s indicator.
- Clear the indicator when a later `tool_progress` for the same `parent_tool_use_id` arrives with neither `subagent_retry` nor `heartbeat: true`, or when the tool’s result message arrives. Frames with `heartbeat: true` report liveness only, so keep the indicator when one arrives. `attempt` can exceed `max_retries` under persistent retry, so don’t derive clearing from the counters.
- Treat `error_category` as a token for choosing your own message text, not as display text. The values are `rate_limit`, `overloaded`, `authentication_failed`, `server_error`, `cloud_credential_error`, and `unknown`. Handle a value you don’t recognize the way you handle `unknown`, because later releases can add values.


[​](#sdkauthstatusmessage)

`SDKAuthStatusMessage`

Emitted during authentication flows.

```python
type SDKAuthStatusMessage = {
  type: "auth_status";
  isAuthenticating: boolean;
  output: string[];
  error?: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdktaskstartedmessage)

`SDKTaskStartedMessage`

Emitted when a task begins. The `task_type` field is `"local_bash"` for Bash commands and [Monitor](#monitor) watches, `"local_agent"` for subagents, or `"remote_agent"`.

```python
type SDKTaskStartedMessage = {
  type: "system";
  subtype: "task_started";
  task_id: string;
  tool_use_id?: string;
  description: string;
  task_type?: string;
  is_backgrounded?: boolean;
  spawn_depth?: number;
  parent_task_id?: string;
  ambient?: boolean;
  uuid: UUID;
  session_id: string;
};
```

`ambient` is `true` for tasks that aren’t part of the session’s work, such as tasks Claude Code runs for its own operation. Live-update watchers are also ambient, including watchers the user asked for. Exclude ambient tasks from activity indicators. The field requires Agent SDK v0.3.247 or later. `ambient` also appears on [`SDKTaskNotificationMessage`](#sdktasknotificationmessage) and on [`SDKBackgroundTasksChangedMessage`](#sdkbackgroundtaskschangedmessage) entries. `is_backgrounded` and `spawn_depth` describe how Claude Code started the task. Both fields require Agent SDK v0.3.238 or later.

- `is_backgrounded`: Claude Code sets it on `"local_agent"` and `"local_bash"` tasks. `true` means the task runs in the background. `false` means the task runs in the foreground, and the tool call that started it stays blocked until the task finishes or moves to the background.
- `spawn_depth`: Claude Code sets it on `"local_agent"` tasks only. A subagent that the main thread spawned has depth `1`. A subagent that a depth `1` subagent spawned has depth `2`, and so on.

A [resumed subagent](agent-sdk-subagents.md#resume-subagents) always reports `is_backgrounded: true`, because Claude Code runs every resumed subagent in the background. When a foreground task moves to the background later, Claude Code reports the new `is_backgrounded` value in a [`task_updated`](#sdktaskupdatedmessage) message rather than sending a second `task_started`. `parent_task_id` holds the `task_id` of the subagent that launched this task. Use it to group each task under the subagent that started it. Claude Code sets it on subagent, Bash, and [Monitor](#monitor) tasks. The field requires Agent SDK v0.3.292 or later. It is absent when:

- The main thread launched the task
- Claude Code no longer tracks the parent task
- A [teammate](../09-Agents-Patterns/agent-teams.md) or an agent inside a workflow launched the task

The parent can be a foreground task or one that already ended, so treat an ID you don’t recognize as no parent.


[​](#sdktaskprogressmessage)

`SDKTaskProgressMessage`

Emitted periodically while a subagent or background task is running. For a subagent task, the `summary` field carries a model-generated progress summary and is populated only when [`agentProgressSummaries`](#options) is enabled. For a [backgrounded MCP tool call](../06-MCP-Tools/General/mcp.md#automatic-backgrounding-of-long-tool-calls), `summary` carries the MCP server’s latest reported progress and doesn’t depend on that option.

```python
type SDKTaskProgressMessage = {
  type: "system";
  subtype: "task_progress";
  task_id: string;
  tool_use_id?: string;
  description: string;
  subagent_type?: string;
  usage: {
    total_tokens: number;
    tool_uses: number;
    duration_ms: number;
  };
  last_tool_name?: string;
  summary?: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdktaskupdatedmessage)

`SDKTaskUpdatedMessage`

Emitted when a background task’s state changes, such as when it transitions from `running` to `completed`. Merge `patch` into your local task map keyed by `task_id`. The `end_time` field is a Unix epoch timestamp in milliseconds, comparable with `Date.now()`.

```python
type SDKTaskUpdatedMessage = {
  type: "system";
  subtype: "task_updated";
  task_id: string;
  patch: {
    status?: "pending" | "running" | "completed" | "failed" | "killed";
    description?: string;
    end_time?: number;
    total_paused_ms?: number;
    error?: string;
    is_backgrounded?: boolean;
  };
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkbackgroundtaskschangedmessage)

`SDKBackgroundTasksChangedMessage`

Emitted whenever the set of live background tasks changes: a task starts, completes, or is killed; a foreground agent is backgrounded; or a task’s `description`, `ambient`, or `parent_task_id` field changes. For the `parent_task_id` field on each entry, see [`SDKTaskStartedMessage`](#sdktaskstartedmessage), which defines it and its version requirement. The `tasks` array is the full live set. Replace any cached set with each payload instead of pairing `task_started` and `task_notification` events, so the next membership change corrects any event you missed. When a task ends, its [`task_updated`](#sdktaskupdatedmessage) and [`task_notification`](#sdktasknotificationmessage) arrive before the `background_tasks_changed` that drops it from the list. Ordering relative to the per-task events is otherwise unspecified. Nothing is emitted at startup. Reset to an empty set whenever the session’s CLI process starts or restarts and let the next membership change repopulate it. When you send a repeated `initialize` control request to a running session, such as with [`reinitialize()`](#query-object) after a transport gap, Claude Code follows the response with a snapshot of the current live set, even when it is empty. A reconnecting host therefore learns what is running without waiting for the next membership change. Before Agent SDK v0.3.239, Claude Code sent no snapshot after a repeated `initialize`. Requires Claude Code v2.1.203 or later.

```python
type SDKBackgroundTasksChangedMessage = {
  type: "system";
  subtype: "background_tasks_changed";
  tasks: {
    task_id: string;
    task_type: string;
    subagent_type?: string;
    description: string;
    parent_task_id?: string;
    ambient?: boolean;
  }[];
  uuid: UUID;
  session_id: string;
};
```

`subagent_type` names the subagent type on entries whose [`task_type`](#sdktaskstartedmessage) is `"local_agent"`, such as `general-purpose` or a custom subagent’s name. The field requires Agent SDK v0.3.293 or later.


[​](#sdkthinkingtokensmessage)

`SDKThinkingTokensMessage`

Emitted while Claude is producing a thinking block, including a redacted one. `estimated_tokens` is a running estimate of the thinking tokens generated so far in the current block, and `estimated_tokens_delta` is the increment carried by this frame. Use these estimates for progress display. When the model or provider reports a breakdown, the final count for the top-level agent loop is the result message’s [`usage.output_tokens_details.thinking_tokens`](#usage), which [doesn’t include subagent tokens](agent-sdk-cost-tracking.md#get-the-total-cost-of-a-query). Requires Claude Code v2.1.153 or later.

```python
type SDKThinkingTokensMessage = {
  type: "system";
  subtype: "thinking_tokens";
  estimated_tokens: number;
  estimated_tokens_delta: number;
  user_message_uuid?: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdksessionstatechangedmessage)

`SDKSessionStateChangedMessage`

Emitted when Claude Code reports the session’s state. To receive these messages, set [`CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1`](../02-Claude-Code-CLI/env-vars.md#variables). Claude Code can report the same state more than once, so read a message as the session’s current state rather than as a transition. The `state` field carries one of these values:

- `running`: the session is working.
- `idle`: Claude Code is waiting for your next prompt.
- `requires_action`: the session is blocked on an answer to a request it sent your host, such as a permission prompt.

A turn’s `idle` message and its `result` message can arrive in either order. To change whether `idle` waits for background work such as a background subagent or a [workflow](../02-Claude-Code-CLI/workflows.md) run, see [`CLAUDE_CODE_BG_TASKS_REPORT_RUNNING`](../02-Claude-Code-CLI/env-vars.md#variables).

```python
type SDKSessionStateChangedMessage = {
  type: "system";
  subtype: "session_state_changed";
  state: "idle" | "running" | "requires_action";
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkfilespersistedevent)

`SDKFilesPersistedEvent`

Emitted when file checkpoints are persisted to disk.

```python
type SDKFilesPersistedEvent = {
  type: "system";
  subtype: "files_persisted";
  files: { filename: string; file_id: string }[];
  failed: { filename: string; error: string }[];
  processed_at: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkratelimitevent)

`SDKRateLimitEvent`

Emitted when the session encounters a rate limit.

```python
type SDKRateLimitEvent = {
  type: "rate_limit_event";
  rate_limit_info: {
    status: "allowed" | "allowed_warning" | "rejected";
    resetsAt?: number;
    utilization?: number;
    errorCode?: "credits_required";
    canUserPurchaseCredits?: boolean;
    hasChargeableSavedPaymentMethod?: boolean;
  };
  uuid: UUID;
  session_id: string;
};
```

When `errorCode` is `"credits_required"`, the rejection is from a claude.ai subscription whose included usage is exhausted, and the session cannot continue until the user buys usage credits. `canUserPurchaseCredits` indicates whether the authenticated user can buy credits for the account, and `hasChargeableSavedPaymentMethod` indicates whether a saved payment method is on file. All three fields are absent on rate-limit events that are not credits-required rejections. Requires Claude Code v2.1.181 or later.


[​](#sdklocalcommandoutputmessage)

`SDKLocalCommandOutputMessage`

Claude Code doesn’t emit this message type. When you send a command such as `/context` or `/usage` as a prompt, its output arrives as an [`SDKAssistantMessage`](#sdkassistantmessage).

```python
type SDKLocalCommandOutputMessage = {
  type: "system";
  subtype: "local_command_output";
  content: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkcommandschangedmessage)

`SDKCommandsChangedMessage`

Emitted when the set of available commands changes mid-session, such as when Claude Code discovers skills as the agent enters a subdirectory. The `commands` array is the full updated list, so replace any cached command list with this payload. Calling [`supportedCommands()`](#query-object) after this message returns the same updated list, because the method tracks the latest push; this requires Agent SDK v0.3.216 or later. In earlier SDK versions, `supportedCommands()` returns the snapshot captured at initialization and never reflects mid-session changes. Claude Code also emits this message when an MCP server’s [prompts](../06-MCP-Tools/General/mcp.md#use-mcp-prompts-as-commands) join or leave the list, for example when a server finishes connecting after the session starts. This requires Claude Code v2.1.281 or later.

```python
type SDKCommandsChangedMessage = {
  type: "system";
  subtype: "commands_changed";
  commands: SlashCommand[];
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkpromptsuggestionmessage)

`SDKPromptSuggestionMessage`

Emitted after a turn when [`promptSuggestions`](#options) is enabled and Claude Code generated a suggestion for that turn. Contains the predicted next user prompt. For the turns that get none, see [When Claude Code skips suggestions](../02-Claude-Code-CLI/interactive-mode.md#when-claude-code-skips-suggestions).

```python
type SDKPromptSuggestionMessage = {
  type: "prompt_suggestion";
  suggestion: string;
  uuid: UUID;
  session_id: string;
};
```


[​](#sdkconversationresetmessage)

`SDKConversationResetMessage`

Emitted when the session’s conversation is replaced without ending the session. In a `query()` call, only `/clear` and its aliases produce this message. Mount an empty transcript under `new_conversation_id` and discard any cached session title.

```python
type SDKConversationResetMessage = {
  type: "conversation_reset";
  new_conversation_id: UUID;
  uuid: UUID;
  session_id: string;
  trigger?: "clear" | "plan_mode_exit" | "fresh_session" | "onboarding";
  user_message_uuid?: string;
  timestamp?: string;
};
```

The optional fields describe the reset:

- `trigger`: what discarded the conversation. Reset your transcript on every `conversation_reset` message, including one where this field is absent or carries a value you don’t recognize.
- `user_message_uuid`: the `uuid` of the user message that carried the `/clear`. Use it to match the reset to that message.
- `timestamp`: when the reset happened, as an ISO 8601 string in UTC. Use it for display, not for ordering messages.

The `trigger`, `user_message_uuid`, and `timestamp` fields require Claude Code v2.1.281 or later. The SDK’s published typings declare `SDKConversationResetMessage` in Claude Code v2.1.203 and later. Before v2.1.203, `SDKMessage` referenced the type without declaring it, so narrowing on `type === "conversation_reset"` failed to typecheck when `skipLibCheck` was disabled.


[​](#aborterror)

`AbortError`

Custom error class for abort operations.

```python
class AbortError extends Error {}
```

`AbortError` is the only error class in the SDK’s typed API. Other failures, such as the Claude Code process exiting or failing to launch, reject the message iteration with errors that carry no SDK class to match on. [Troubleshooting](agent-sdk-troubleshooting.md) keys those errors by message, with the cause and fix for each.


[​](#sandbox-configuration)

Sandbox Configuration


[​](#sandboxsettings)

`SandboxSettings`

Configuration for sandbox behavior. Use this to enable command sandboxing and configure network restrictions programmatically.

```python
type SandboxSettings = {
  enabled?: boolean;
  failIfUnavailable?: boolean;
  autoAllowBashIfSandboxed?: boolean;
  excludedCommands?: string[];
  allowUnsandboxedCommands?: boolean;
  network?: SandboxNetworkConfig;
  filesystem?: SandboxFilesystemConfig;
  ignoreViolations?: Record<string, string[]>;
  enableWeakerNestedSandbox?: boolean;
  ripgrep?: { command: string; args?: string[] };
};
```

| Property                    | Type                                                  | Default     | Description                                                                                                                                                                                                                                       |
|:----------------------------|:------------------------------------------------------|:------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `enabled`                   | `boolean`                                             | `false`     | Enable sandbox mode for command execution                                                                                                                                                                                                         |
| `failIfUnavailable`         | `boolean`                                             | `true`      | Stop at startup if `enabled` is `true` but the sandbox can’t start. Set `false` to fall back to unsandboxed execution with a warning on stderr                                                                                                    |
| `autoAllowBashIfSandboxed`  | `boolean`                                             | `true`      | Auto-approve Bash commands when sandbox is enabled                                                                                                                                                                                                |
| `excludedCommands`          | `string[]`                                            | `[]`        | Commands that bypass sandbox restrictions, such as `['docker *']`. These run unsandboxed automatically without model involvement; [`sandbox.excludedCommands`](../02-Claude-Code-CLI/settings-reference.md#sandbox-excludedcommands) covers when an entry applies |
| `allowUnsandboxedCommands`  | `boolean`                                             | `true`      | Allow the model to request running commands outside the sandbox. When `true`, the model can set `dangerouslyDisableSandbox` in tool input, which falls back to the [permissions system](#permissions-fallback-for-unsandboxed-commands)           |
| `network`                   | [`SandboxNetworkConfig`](#sandboxnetworkconfig)       | `undefined` | Network-specific sandbox configuration                                                                                                                                                                                                            |
| `filesystem`                | [`SandboxFilesystemConfig`](#sandboxfilesystemconfig) | `undefined` | Filesystem-specific sandbox configuration for read/write restrictions                                                                                                                                                                             |
| `ignoreViolations`          | `Record<string, string[]>`                            | `undefined` | Map of command substrings, or `*` for every command, to substrings of the violation text to ignore, such as `{ "*": ['/etc/hosts'] }`; see [`sandbox.ignoreViolations`](../02-Claude-Code-CLI/settings-reference.md#sandbox-ignoreviolations)                     |
| `enableWeakerNestedSandbox` | `boolean`                                             | `false`     | Enable a weaker nested sandbox for compatibility                                                                                                                                                                                                  |
| `ripgrep`                   | `{ command: string; args?: string[] }`                | `undefined` | Custom ripgrep binary configuration for sandbox environments                                                                                                                                                                                      |

The sandbox depends on platform support and, on Linux, tools like `bubblewrap` and `socat`. When `enabled` is `true` and the sandbox can’t start, `query()` reports a `result` message with `subtype: "error_during_execution"` and the reason in `errors`. For a single message `query()` call, the SDK throws after yielding that error result, so wrap the loop in a try block to continue past it. See [Handle the result](agent-sdk-agent-loop.md#handle-the-result) for the error contract.To run unsandboxed instead, set `failIfUnavailable: false`.


[​](#example-usage)

Example usage

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({
    prompt: "Build and test my project",
    options: {
      sandbox: {
        enabled: true,
        autoAllowBashIfSandboxed: true,
        network: {
          allowLocalBinding: true
        }
      }
    }
  })) {
    if ("result" in message) console.log(message.result);
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // such as when the sandbox can't start (failIfUnavailable defaults to true).
  console.log(`Session ended with an error: ${error}`);
}
```

**Unix socket security:** The `allowUnixSockets` option can grant access to system services that reach outside the sandbox. For example, allowing `/var/run/docker.sock` effectively grants full host system access through the Docker API, bypassing sandbox isolation. Only allow Unix sockets that are strictly necessary and understand the security implications of each.


[​](#sandboxnetworkconfig)

`SandboxNetworkConfig`

Network-specific configuration for sandbox mode. These settings apply to sandboxed Bash commands when `enabled` is `true` in the parent [`SandboxSettings`](#sandboxsettings). They do not restrict the WebFetch tool, which uses [permission rules](../02-Claude-Code-CLI/permissions.md#webfetch) instead.

```python
type SandboxNetworkConfig = {
  allowedDomains?: string[];
  deniedDomains?: string[];
  strictAllowlist?: boolean;
  allowManagedDomainsOnly?: boolean;
  allowLocalBinding?: boolean;
  allowUnixSockets?: string[];
  allowAllUnixSockets?: boolean;
  httpProxyPort?: number;
  socksProxyPort?: number;
};
```

| Property                  | Type       | Default     | Description                                                                                                                                                                                                                                                                                                                                                          |
|:--------------------------|:-----------|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allowedDomains`          | `string[]` | `[]`        | Domain names that sandboxed processes can access                                                                                                                                                                                                                                                                                                                     |
| `deniedDomains`           | `string[]` | `[]`        | Domain names that sandboxed processes cannot access. Takes precedence over `allowedDomains`                                                                                                                                                                                                                                                                          |
| `strictAllowlist`         | `boolean`  | `false`     | Deny sandboxed commands access to hosts outside the [network allowlist](../02-Claude-Code-CLI/sandboxing.md#network-isolation) instead of prompting. Enforced for sandboxed commands only; in-process tools such as WebFetch aren’t gated by it. Only honored from user, managed, or CLI `--settings` settings; project settings are ignored. Requires Claude Code v2.1.219 or later |
| `allowManagedDomainsOnly` | `boolean`  | `false`     | Managed-settings only. When set in [managed settings](../13-Enterprise-Admin/managed-settings.md), only `allowedDomains` entries and `WebFetch(domain:...)` allow rules from managed settings are honored, and allow entries from user, project, or local settings are ignored. From the SDK, pass it through the [`managedSettings`](#options) option                                |
| `allowLocalBinding`       | `boolean`  | `false`     | Allow processes to bind to local ports (for example, for dev servers)                                                                                                                                                                                                                                                                                                |
| `allowUnixSockets`        | `string[]` | `[]`        | Unix socket paths that processes can access (for example, Docker socket)                                                                                                                                                                                                                                                                                             |
| `allowAllUnixSockets`     | `boolean`  | `false`     | Allow access to all Unix sockets                                                                                                                                                                                                                                                                                                                                     |
| `httpProxyPort`           | `number`   | `undefined` | HTTP proxy port for network requests                                                                                                                                                                                                                                                                                                                                 |
| `socksProxyPort`          | `number`   | `undefined` | SOCKS proxy port for network requests                                                                                                                                                                                                                                                                                                                                |

The built-in sandbox proxy enforces `allowedDomains` based on the requested hostname and does not terminate or inspect TLS traffic, so techniques such as [domain fronting](https://en.wikipedia.org/wiki/Domain_fronting) can potentially bypass it. See [Sandboxing security limitations](../02-Claude-Code-CLI/sandboxing.md#security-limitations) for details and [Secure deployment](agent-sdk-secure-deployment.md#traffic-forwarding) for configuring a TLS-terminating proxy.


[​](#sandboxfilesystemconfig)

`SandboxFilesystemConfig`

Filesystem-specific configuration for sandbox mode.

```python
type SandboxFilesystemConfig = {
  allowWrite?: string[];
  denyWrite?: string[];
  denyRead?: string[];
};
```

| Property     | Type       | Default | Description                                 |
|:-------------|:-----------|:--------|:--------------------------------------------|
| `allowWrite` | `string[]` | `[]`    | File path patterns to allow write access to |
| `denyWrite`  | `string[]` | `[]`    | File path patterns to deny write access to  |
| `denyRead`   | `string[]` | `[]`    | File path patterns to deny read access to   |


[​](#permissions-fallback-for-unsandboxed-commands)

Permissions Fallback for Unsandboxed Commands

When `allowUnsandboxedCommands` is enabled, the model can request to run commands outside the sandbox by setting `dangerouslyDisableSandbox: true` in the tool input. These requests fall back to the existing permissions system, meaning your `canUseTool` handler is invoked, allowing you to implement custom authorization logic. Your `excludedCommands` entries instead take a call out of the sandbox with no model involvement; [`sandbox.excludedCommands`](../02-Claude-Code-CLI/settings-reference.md#sandbox-excludedcommands) covers when an entry applies. In the example below, `isCommandAuthorized` stands in for an authorization check you define.

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Deploy my application",
  options: {
    sandbox: {
      enabled: true,
      allowUnsandboxedCommands: true // Model can request unsandboxed execution
    },
    permissionMode: "default",
    canUseTool: async (tool, input) => {
      // Check if the model is requesting to bypass the sandbox
      if (tool === "Bash" && input.dangerouslyDisableSandbox) {
        // The model is requesting to run this command outside the sandbox
        console.log(`Unsandboxed command requested: ${input.command}`);

        if (isCommandAuthorized(input.command)) {
          return { behavior: "allow" as const, updatedInput: input };
        }
        return {
          behavior: "deny" as const,
          message: "Command not authorized for unsandboxed execution"
        };
      }
      return { behavior: "allow" as const, updatedInput: input };
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}
```

Commands running with `dangerouslyDisableSandbox: true` have full system access. Ensure your `canUseTool` handler validates these requests carefully.If `permissionMode` is set to `bypassPermissions` and `allowUnsandboxedCommands` is enabled, the model can autonomously execute commands outside the sandbox without approval prompts, apart from the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves). Review enterprise hardening guidance before enabling this combination.


[​](#see-also)

See also

- [SDK overview](agent-sdk-overview.md) - General SDK concepts
- [Python SDK reference](agent-sdk-python.md) - Python SDK documentation
- [CLI reference](../02-Claude-Code-CLI/cli-reference.md) - Command-line interface
- [Common workflows](../02-Claude-Code-CLI/common-workflows.md) - Step-by-step guides

---

## Handle approvals and user input - Claude Code Docs

Source: https://code.claude.com/docs/en/agent-sdk/user-input
Path: 05-Agent-SDK/agent-sdk-user-input.md

## On this page

- [Detect when Claude needs input](#detect-when-claude-needs-input)
- [Handle tool approval requests](#handle-tool-approval-requests)
  - [Respond to tool requests](#respond-to-tool-requests)
- [Handle clarifying questions](#handle-clarifying-questions)
  - [Question format](#question-format)
  - [Option previews (TypeScript)](#option-previews-typescript)
  - [Response format](#response-format)
  - [Support free-text input](#support-free-text-input)
  - [Complete example](#complete-example)
- [Limitations](#limitations)
- [Other ways to get user input](#other-ways-to-get-user-input)
  - [Streaming input](#streaming-input)
  - [Custom tools](#custom-tools)
- [Related resources](#related-resources)

Input and output

# Handle approvals and user input

Copy pageCopy page

Surface Claude’s approval requests and clarifying questions to users, then return their decisions to the SDK.

Copy pageCopy page

While working on a task, Claude sometimes needs to check in with users. It might need permission before deleting files, or need to ask which database to use for a new project. Your application needs to surface these requests to users so Claude can continue with their input. Claude requests user input in two situations: when it needs **permission to use a tool** (like deleting files or running commands), and when it has **clarifying questions** (via the `AskUserQuestion` tool). Both trigger your `canUseTool` callback, which pauses execution until you return a response. This is different from normal conversation turns where Claude finishes and waits for your next message. For clarifying questions, Claude generates the questions and options. Your role is to present them to users and return their selections. You can’t add your own questions to this flow; if you need to ask users something yourself, do that separately in your application logic. The callback can stay pending indefinitely. Execution remains paused until your callback returns. If a user might take longer to respond than your process can reasonably stay running, register a [`PreToolUse` hook](agent-sdk-hooks.md) that returns the [`defer` decision](../07-Hooks/hooks.md#defer-a-tool-call-for-later) instead of waiting in the callback, so the process can exit and resume later from the persisted session. This guide shows you how to detect each type of request and respond appropriately.


[​](#detect-when-claude-needs-input)

Detect when Claude needs input

Pass a `canUseTool` callback in your query options. The callback fires whenever Claude needs user input, receiving the tool name and input as arguments:

Python

TypeScript

```python
from claude_agent_sdk import ClaudeAgentOptions


async def handle_tool_request(tool_name, input_data, context):
    # Prompt user and return allow or deny
    ...


options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
```

```python
import type { CanUseTool } from "@anthropic-ai/claude-agent-sdk";

const handleToolRequest: CanUseTool = async (toolName, input, options) => {
  // options includes { signal: AbortSignal, suggestions?: PermissionUpdate[] }
  // Prompt the user here, then return allow or deny
  return { behavior: "deny", message: "User declined" };
};

const options = { canUseTool: handleToolRequest };
```

The callback fires in two cases:

1.  **Tool needs approval**: Claude wants to use a tool that isn’t auto-approved by a [permission rule](agent-sdk-permissions.md) or permission mode. Check `tool_name` for the tool (e.g., `"Bash"`, `"Write"`).
2.  **Claude asks a question**: Claude calls the `AskUserQuestion` tool. Check if `tool_name == "AskUserQuestion"` to handle it differently. If you specify a `tools` array, include `AskUserQuestion` for this to work. See [Handle clarifying questions](#handle-clarifying-questions) for details.

**The callback never fires for auto-approved tools.** Any approval earlier in the [permission evaluation flow](agent-sdk-permissions.md#how-permissions-are-evaluated), an allow rule or a mode like `acceptEdits` or `bypassPermissions`, resolves the call before `canUseTool` is consulted. If you list a tool bare in `allowed_tools`, a `canUseTool` check for that tool runs only when the [evaluation flow](agent-sdk-permissions.md#how-permissions-are-evaluated) routes the call back to a prompt, such as an ask rule or `plan` mode. For logic that must apply to every tool call, use a [`PreToolUse` hook](agent-sdk-hooks.md), which executes before the rest of the flow and can allow, deny, or modify requests.An allow rule doesn’t pre-approve the [actions no mode auto-approves](../02-Claude-Code-CLI/permission-modes.md#actions-no-mode-auto-approves); see [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) for which of them reach the callback and what happens in `dontAsk` and `auto` mode.

You can also use the [`PermissionRequest` hook](agent-sdk-hooks.md#available-hooks) to send external notifications (Slack, email, push) when Claude is waiting for approval.


[​](#handle-tool-approval-requests)

Handle tool approval requests

Once you’ve passed a `canUseTool` callback in your query options, it fires when Claude wants to use a tool that nothing earlier in the permission flow has approved. In some configurations, such as `dontAsk` mode, Claude Code doesn’t call it; the last step of [How permissions are evaluated](agent-sdk-permissions.md#how-permissions-are-evaluated) lists them and says what happens to the call instead. Your callback receives three arguments:

| Argument                            | Description                                                                                                                                                                                                                                                                                                                                |
|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `toolName`                          | The name of the tool Claude wants to use (for example, `"Bash"`, `"Write"`, `"Edit"`)                                                                                                                                                                                                                                                      |
| `input`                             | The parameters Claude is passing to the tool. Contents vary by tool.                                                                                                                                                                                                                                                                       |
| `options` (TS) / `context` (Python) | Additional context including optional `suggestions` (proposed `PermissionUpdate` entries to avoid re-prompting) and a cancellation signal. In TypeScript, `signal` is an `AbortSignal`; in Python, the signal field is reserved for future use. See [`ToolPermissionContext`](agent-sdk-python.md#toolpermissioncontext) for Python. |

The `input` object contains tool-specific parameters. Common examples:

| Tool    | Input fields                            |
|---------|-----------------------------------------|
| `Bash`  | `command`, `description`, `timeout`     |
| `Write` | `file_path`, `content`                  |
| `Edit`  | `file_path`, `old_string`, `new_string` |
| `Read`  | `file_path`, `offset`, `limit`          |

See the SDK reference for complete input schemas: [Python](agent-sdk-python.md#tool-input%2Foutput-types) \| [TypeScript](agent-sdk-typescript.md#tool-input-types). You can display this information to the user so they can decide whether to allow or reject the action, then return the appropriate response. The following example asks Claude to create and delete a test file. When Claude attempts each operation, the callback prints the tool request to the terminal and prompts for y/n approval.

Python

TypeScript

```python
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from claude_agent_sdk.types import (
    HookMatcher,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


async def can_use_tool(
    tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    # Display the tool request
    print(f"\nTool: {tool_name}")
    if tool_name == "Bash":
        print(f"Command: {input_data.get('command')}")
        if input_data.get("description"):
            print(f"Description: {input_data.get('description')}")
    else:
        print(f"Input: {input_data}")

    # Get user approval
    response = input("Allow this action? (y/n): ")

    # Return allow or deny based on user's response
    if response.lower() == "y":
        # Allow: tool executes with the original (or modified) input
        return PermissionResultAllow(updated_input=input_data)
    else:
        # Deny: tool doesn't execute, Claude sees the message
        return PermissionResultDeny(message="User denied this action")


# Required workaround: dummy hook keeps the stream open for can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}


async def prompt_stream():
    yield {
        "type": "user",
        "message": {
            "role": "user",
            "content": "Create a test file in /tmp and then delete it",
        },
    }


async def main():
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            can_use_tool=can_use_tool,
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";
import * as readline from "readline";

// Helper to prompt user for input in the terminal
function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  return new Promise((resolve) =>
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer);
    })
  );
}

for await (const message of query({
  prompt: "Create a test file in /tmp and then delete it",
  options: {
    canUseTool: async (toolName, input) => {
      // Display the tool request
      console.log(`\nTool: ${toolName}`);
      if (toolName === "Bash") {
        console.log(`Command: ${input.command}`);
        if (input.description) console.log(`Description: ${input.description}`);
      } else {
        console.log(`Input: ${JSON.stringify(input, null, 2)}`);
      }

      // Get user approval
      const response = await prompt("Allow this action? (y/n): ");

      // Return allow or deny based on user's response
      if (response.toLowerCase() === "y") {
        // Allow: tool executes with the original (or modified) input
        return { behavior: "allow", updatedInput: input };
      } else {
        // Deny: tool doesn't execute, Claude sees the message
        return { behavior: "deny", message: "User denied this action" };
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}
```

This example uses a `y/n` flow where any input other than `y` is treated as a denial. In practice, you might build a richer UI that lets users modify the request, provide feedback, or redirect Claude entirely. See [Respond to tool requests](#respond-to-tool-requests) for all the ways you can respond.


[​](#respond-to-tool-requests)

Respond to tool requests

Your callback returns one of two response types:

| Response  | Python                                     | TypeScript                            |
|-----------|--------------------------------------------|---------------------------------------|
| **Allow** | `PermissionResultAllow(updated_input=...)` | `{ behavior: "allow", updatedInput }` |
| **Deny**  | `PermissionResultDeny(message=...)`        | `{ behavior: "deny", message }`       |

When allowing, the tool runs with the input Claude requested unless you return a modified input, `updatedInput` in TypeScript or `updated_input` in Python. Before v2.1.207, Claude Code rejected an allow result that omitted `updatedInput` and denied the tool call with a validation error. When denying, provide a message explaining why. Claude sees this message and may adjust its approach. Beyond allowing or denying, you can modify the tool’s input or provide context that helps Claude adjust its approach:

- **Approve**: let the tool execute as Claude requested
- **Approve with changes**: modify the input before execution (for example, sanitize paths, add constraints)
- **Approve and remember**: echo a suggested permission rule back so matching calls skip the prompt next time
- **Reject**: block the tool and tell Claude why
- **Suggest alternative**: block but guide Claude toward what the user wants instead
- **Redirect entirely**: use [streaming input](agent-sdk-streaming-vs-single-mode.md) to send Claude a completely new instruction

The `ask_user` and `askUser` helpers in the following snippets stand in for your application’s own prompt UI.

- Approve

- Approve with changes

- Approve and remember

- Reject

- Suggest alternative

- Redirect entirely

The user approves the action as-is. Pass through the `input` from your callback unchanged and the tool executes exactly as Claude requested.

Python

TypeScript

```python
async def can_use_tool(tool_name, input_data, context):
    print(f"Claude wants to use {tool_name}")
    approved = await ask_user("Allow this action?")

    if approved:
        return PermissionResultAllow(updated_input=input_data)
    return PermissionResultDeny(message="User declined")
```

```python
canUseTool: async (toolName, input) => {
  console.log(`Claude wants to use ${toolName}`);
  const approved = await askUser("Allow this action?");

  if (approved) {
    return { behavior: "allow", updatedInput: input };
  }
  return { behavior: "deny", message: "User declined" };
};
```

The user approves but wants to modify the request first. You can change the input before the tool executes. Claude sees the result but isn’t told you changed anything. Useful for sanitizing parameters, adding constraints, or scoping access.

Python

TypeScript

```python
async def can_use_tool(tool_name, input_data, context):
    if tool_name == "Bash":
        # User approved, but scope all commands to sandbox
        sandboxed_input = {**input_data}
        sandboxed_input["command"] = input_data["command"].replace(
            "/tmp", "/tmp/sandbox"
        )
        return PermissionResultAllow(updated_input=sandboxed_input)
    return PermissionResultAllow(updated_input=input_data)
```

```python
canUseTool: async (toolName, input) => {
  if (toolName === "Bash") {
    // User approved, but scope all commands to sandbox
    const sandboxedInput = {
      ...input,
      command: input.command.replace("/tmp", "/tmp/sandbox")
    };
    return { behavior: "allow", updatedInput: sandboxedInput };
  }
  return { behavior: "allow", updatedInput: input };
};
```

The user approves and doesn’t want to be asked again for this kind of call. The third callback argument carries `suggestions`, an array of ready-made [`PermissionUpdate`](agent-sdk-typescript.md#permissionupdate) entries. Echo one back in `updatedPermissions` to apply it. A suggestion with the `localSettings` destination writes the rule to `.claude/settings.local.json` so future sessions skip the prompt for matching calls.In TypeScript, skip the always-allow choice for a request whose options carry [`suppressAlwaysAllowRule: true`](agent-sdk-typescript.md#canusetool). The hint requires Agent SDK v0.3.268 or later, and the Python `context` doesn’t carry it.The Python example requires `claude-agent-sdk` 0.1.80 or later.

Python

TypeScript

```python
async def can_use_tool(tool_name, input_data, context):
    choice = await ask_user(f"Allow {tool_name}?", ["once", "always", "no"])

    if choice == "always":
        persist = [
            s for s in context.suggestions if s.destination == "localSettings"
        ]
        return PermissionResultAllow(
            updated_input=input_data, updated_permissions=persist
        )
    if choice == "once":
        return PermissionResultAllow(updated_input=input_data)
    return PermissionResultDeny(message="User declined")
```

```python
canUseTool: async (toolName, input, { suggestions = [] }) => {
  const choice = await askUser(`Allow ${toolName}?`, ["once", "always", "no"]);

  if (choice === "always") {
    const persist = suggestions.filter(
      (s) => s.destination === "localSettings"
    );
    return {
      behavior: "allow",
      updatedInput: input,
      updatedPermissions: persist
    };
  }
  if (choice === "once") {
    return { behavior: "allow", updatedInput: input };
  }
  return { behavior: "deny", message: "User declined" };
};
```

The user doesn’t want this action to happen. Block the tool and provide a message explaining why. Claude sees this message and may try a different approach.

Python

TypeScript

```python
async def can_use_tool(tool_name, input_data, context):
    approved = await ask_user(f"Allow {tool_name}?")

    if not approved:
        return PermissionResultDeny(message="User rejected this action")
    return PermissionResultAllow(updated_input=input_data)
```

```python
canUseTool: async (toolName, input) => {
  const approved = await askUser(`Allow ${toolName}?`);

  if (!approved) {
    return {
      behavior: "deny",
      message: "User rejected this action"
    };
  }
  return { behavior: "allow", updatedInput: input };
};
```

The user doesn’t want this specific action, but has a different idea. Block the tool and include guidance in your message. Claude will read this and decide how to proceed based on your feedback.

Python

TypeScript

```python
async def can_use_tool(tool_name, input_data, context):
    if tool_name == "Bash" and "rm" in input_data.get("command", ""):
        # User doesn't want to delete, suggest archiving instead
        return PermissionResultDeny(
            message="User doesn't want to delete files. They asked if you could compress them into an archive instead."
        )
    return PermissionResultAllow(updated_input=input_data)
```

```python
canUseTool: async (toolName, input) => {
  if (toolName === "Bash" && input.command.includes("rm")) {
    // User doesn't want to delete, suggest archiving instead
    return {
      behavior: "deny",
      message:
        "User doesn't want to delete files. They asked if you could compress them into an archive instead."
    };
  }
  return { behavior: "allow", updatedInput: input };
};
```

For a complete change of direction (not just a nudge), use [streaming input](agent-sdk-streaming-vs-single-mode.md) to send Claude a new instruction directly. This bypasses the current tool request and gives Claude entirely new instructions to follow.


[​](#handle-clarifying-questions)

Handle clarifying questions

When Claude needs more direction on a task with multiple valid approaches, it calls the `AskUserQuestion` tool. This triggers your `canUseTool` callback with `toolName` set to `AskUserQuestion`. The input contains Claude’s questions as multiple-choice options, which you display to the user and return their selections.

Clarifying questions are especially common in [`plan` mode](agent-sdk-permissions.md#plan-mode-plan), where Claude explores the codebase and asks questions before proposing a plan. This makes plan mode ideal for interactive workflows where you want Claude to gather requirements before making changes.

The following steps show how to handle clarifying questions:

1

Pass a canUseTool callback

Pass a `canUseTool` callback in your query options. By default, `AskUserQuestion` is available. If you specify a `tools` array to restrict Claude’s capabilities (for example, a read-only agent with only `Read`, `Glob`, and `Grep`), include `AskUserQuestion` in that array. Otherwise, Claude won’t be able to ask clarifying questions:

Python

TypeScript

```python
async for message in query(
    prompt="Analyze this codebase",
    options=ClaudeAgentOptions(
        # Include AskUserQuestion in your tools list
        tools=["Read", "Glob", "Grep", "AskUserQuestion"],
        can_use_tool=can_use_tool,
    ),
):
    print(message)
```

```python
for await (const message of query({
  prompt: "Analyze this codebase",
  options: {
    // Include AskUserQuestion in your tools list
    tools: ["Read", "Glob", "Grep", "AskUserQuestion"],
    canUseTool: async (toolName, input) => {
      // Placeholder that approves every call. The Detect AskUserQuestion step replaces it.
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  console.log(message);
}
```

2

Detect AskUserQuestion

In your callback, check if `toolName` equals `AskUserQuestion` to handle it differently from other tools:

Python

TypeScript

```python
async def can_use_tool(tool_name: str, input_data: dict, context):
    if tool_name == "AskUserQuestion":
        # Your implementation to collect answers from the user
        return await handle_clarifying_questions(input_data)
    # Handle other tools normally
    return await prompt_for_approval(tool_name, input_data)
```

```python
canUseTool: async (toolName, input) => {
  if (toolName === "AskUserQuestion") {
    // Your implementation to collect answers from the user
    return handleClarifyingQuestions(input);
  }
  // Handle other tools normally
  return promptForApproval(toolName, input);
};
```

3

Parse the question input

The input contains Claude’s questions in a `questions` array. Each question has a `question` (the text to display), `options` (the choices), and `multiSelect` (whether multiple selections are allowed):

```python
{
  "questions": [
    {
      "question": "How should I format the output?",
      "header": "Format",
      "options": [
        { "label": "Summary", "description": "Brief overview" },
        { "label": "Detailed", "description": "Full explanation" }
      ],
      "multiSelect": false
    },
    {
      "question": "Which sections should I include?",
      "header": "Sections",
      "options": [
        { "label": "Introduction", "description": "Opening context" },
        { "label": "Conclusion", "description": "Final summary" }
      ],
      "multiSelect": true
    }
  ]
}
```

See [Question format](#question-format) for full field descriptions.

4

Collect answers from the user

Present the questions to the user and collect their selections. How you do this depends on your application: a terminal prompt, a web form, a mobile dialog, etc.

5

Return answers to Claude

Build the `answers` object as a record where each key is the `question` text and each value is the selected option’s `label`:

| From the question object                                            | Use as |
|---------------------------------------------------------------------|--------|
| `question` field (for example, `"How should I format the output?"`) | Key    |
| Selected option’s `label` field (for example, `"Summary"`)          | Value  |

For multi-select questions, pass an array of labels or join them with `", "`. If you [support free-text input](#support-free-text-input), use the user’s custom text as the value.

Python

TypeScript

```python
return PermissionResultAllow(
    updated_input={
        "questions": input_data.get("questions", []),
        "answers": {
            "How should I format the output?": "Summary",
            "Which sections should I include?": ["Introduction", "Conclusion"],
        },
    }
)
```

```python
return {
  behavior: "allow",
  updatedInput: {
    questions: input.questions,
    answers: {
      "How should I format the output?": "Summary",
      "Which sections should I include?": "Introduction, Conclusion"
    }
  }
};
```


[​](#question-format)

Question format

The input contains Claude’s generated questions in a `questions` array. Each question has these fields:

| Field         | Description                                                                                                                                      |
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| `question`    | The full question text to display                                                                                                                |
| `header`      | Short label for the question (max 12 characters)                                                                                                 |
| `options`     | Array of 2-4 choices, each with `label` and `description`. TypeScript: optionally `preview`. See [Option previews](#option-previews-typescript). |
| `multiSelect` | If `true`, users can select multiple options                                                                                                     |

The structure your callback receives:

```python
{
  "questions": [
    {
      "question": "How should I format the output?",
      "header": "Format",
      "options": [
        { "label": "Summary", "description": "Brief overview of key points" },
        { "label": "Detailed", "description": "Full explanation with examples" }
      ],
      "multiSelect": false
    }
  ]
}
```


[​](#option-previews-typescript)

Option previews (TypeScript)

`toolConfig.askUserQuestion.previewFormat` adds a `preview` field to each option so your app can show a visual mockup alongside the label. Without this setting, Claude does not generate previews and the field is absent.

| `previewFormat` | `preview` contains                                                                                            |
|:----------------|:--------------------------------------------------------------------------------------------------------------|
| unset (default) | Field is absent. Claude does not generate previews.                                                           |
| `"markdown"`    | ASCII art and fenced code blocks                                                                              |
| `"html"`        | A styled `<div>` fragment (the SDK rejects `<script>`, `<style>`, and `<!DOCTYPE>` before your callback runs) |

The format applies to all questions in the session. Claude includes `preview` on options where a visual comparison helps (layout choices, color schemes) and omits it where one wouldn’t (yes/no confirmations, text-only choices). Check for `undefined` before rendering.

```python
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Help me choose a card layout",
  options: {
    toolConfig: {
      askUserQuestion: { previewFormat: "html" }
    },
    canUseTool: async (toolName, input) => {
      // input.questions[].options[].preview is an HTML string or undefined
      return { behavior: "allow", updatedInput: input };
    }
  }
})) {
  // ...
}
```

An option with an HTML preview:

```python
{
  "label": "Compact",
  "description": "Title and metric value only",
  "preview": "<div style=\"padding:12px;border:1px solid #ddd;border-radius:8px\"><div style=\"font-size:12px;color:#666\">Active users</div><div style=\"font-size:28px;font-weight:600\">1,284</div></div>"
}
```


[​](#response-format)

Response format

Return an `answers` object mapping each question’s `question` field to the selected option’s `label`:

| Field       | Description                                                                          |
|-------------|--------------------------------------------------------------------------------------|
| `questions` | Pass through the original questions array (required for tool processing)             |
| `answers`   | Object where keys are question text and values are selected labels                   |
| `response`  | Optional freeform reply the user typed instead of answering the structured questions |

For multi-select questions, pass an array of labels or join them with `", "`. For per-question free text such as an “Other” option, put the user’s text in `answers[question]` as shown in [Support free-text input](#support-free-text-input). Set `response` only when your UI lets the user dismiss the question card and type a general reply that isn’t an answer to any specific question. When `response` is set, Claude receives “The user responded: …” instead of the per-question answer list.

```python
{
  "questions": [
    // ...
  ],
  "answers": {
    "How should I format the output?": "Summary",
    "Which sections should I include?": ["Introduction", "Conclusion"]
  }
}
```


[​](#support-free-text-input)

Support free-text input

Claude’s predefined options won’t always cover what users want. To let users type their own answer:

- Display an additional “Other” choice after Claude’s options that accepts text input
- Use the user’s custom text as the answer value (not the word “Other”)

See the [complete example](#complete-example) below for a full implementation.


[​](#complete-example)

Complete example

Claude asks clarifying questions when it needs user input to proceed. For example, when asked to help decide on a tech stack for a mobile app, Claude might ask about cross-platform vs native, backend preferences, or target platforms. These questions help Claude make decisions that match the user’s preferences rather than guessing. This example handles those questions in a terminal application. Here’s what happens at each step:

1.  **Route the request**: The `canUseTool` callback checks if the tool name is `"AskUserQuestion"` and routes to a dedicated handler
2.  **Display questions**: The handler loops through the `questions` array and prints each question with numbered options
3.  **Collect input**: The user can enter a number to select an option, or type free text directly (for example, “jquery”, “i don’t know”)
4.  **Map answers**: The code checks if input is numeric (uses the option’s label) or free text (uses the text directly)
5.  **Return to Claude**: The response includes both the original `questions` array and the `answers` mapping

Save the TypeScript version as `ask.ts` and run it with `npx tsx ask.ts`, or save the Python version as `ask.py` and run it with `python ask.py`.

Python

TypeScript

```python
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from claude_agent_sdk.types import HookMatcher, PermissionResultAllow


def parse_response(response: str, options: list) -> str:
    """Parse user input as option number(s) or free text."""
    try:
        indices = [int(s.strip()) - 1 for s in response.split(",")]
        labels = [options[i]["label"] for i in indices if 0 <= i < len(options)]
        return ", ".join(labels) if labels else response
    except ValueError:
        return response


async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow:
    """Display Claude's questions and collect user answers."""
    answers = {}

    for q in input_data.get("questions", []):
        print(f"\n{q['header']}: {q['question']}")

        options = q["options"]
        for i, opt in enumerate(options):
            print(f"  {i + 1}. {opt['label']} - {opt['description']}")
        if q.get("multiSelect"):
            print("  (Enter numbers separated by commas, or type your own answer)")
        else:
            print("  (Enter a number, or type your own answer)")

        response = input("Your choice: ").strip()
        answers[q["question"]] = parse_response(response, options)

    return PermissionResultAllow(
        updated_input={
            "questions": input_data.get("questions", []),
            "answers": answers,
        }
    )


async def can_use_tool(
    tool_name: str, input_data: dict, context
) -> PermissionResultAllow:
    # Route AskUserQuestion to our question handler
    if tool_name == "AskUserQuestion":
        return await handle_ask_user_question(input_data)
    # Auto-approve other tools for this example
    return PermissionResultAllow(updated_input=input_data)


async def prompt_stream():
    yield {
        "type": "user",
        "message": {
            "role": "user",
            "content": "Help me decide on the tech stack for a new mobile app",
        },
    }


# Required workaround: dummy hook keeps the stream open for can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}


async def main():
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            can_use_tool=can_use_tool,
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
```

```python
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { PermissionResult } from "@anthropic-ai/claude-agent-sdk";
import * as readline from "readline/promises";

// Helper to prompt user for input in the terminal
async function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const answer = await rl.question(question);
  rl.close();
  return answer;
}

// Parse user input as option number(s) or free text
function parseResponse(response: string, options: any[]): string {
  const indices = response.split(",").map((s) => parseInt(s.trim()) - 1);
  const labels = indices
    .filter((i) => !isNaN(i) && i >= 0 && i < options.length)
    .map((i) => options[i].label);
  return labels.length > 0 ? labels.join(", ") : response;
}

// Display Claude's questions and collect user answers
async function handleAskUserQuestion(input: any): Promise<PermissionResult> {
  const answers: Record<string, string> = {};

  for (const q of input.questions) {
    console.log(`\n${q.header}: ${q.question}`);

    const options = q.options;
    options.forEach((opt: any, i: number) => {
      console.log(`  ${i + 1}. ${opt.label} - ${opt.description}`);
    });
    if (q.multiSelect) {
      console.log("  (Enter numbers separated by commas, or type your own answer)");
    } else {
      console.log("  (Enter a number, or type your own answer)");
    }

    const response = (await prompt("Your choice: ")).trim();
    answers[q.question] = parseResponse(response, options);
  }

  // Return the answers to Claude (must include original questions)
  return {
    behavior: "allow",
    updatedInput: { questions: input.questions, answers }
  };
}

async function main() {
  for await (const message of query({
    prompt: "Help me decide on the tech stack for a new mobile app",
    options: {
      canUseTool: async (toolName, input) => {
        // Route AskUserQuestion to our question handler
        if (toolName === "AskUserQuestion") {
          return handleAskUserQuestion(input);
        }
        // Auto-approve other tools for this example
        return { behavior: "allow", updatedInput: input };
      }
    }
  })) {
    if ("result" in message) console.log(message.result);
  }
}

main();
```


[​](#limitations)

Limitations

- **Subagents**: `AskUserQuestion` is not currently available in subagents spawned via the Agent tool
- **Question limits**: each `AskUserQuestion` call supports 1-4 questions with 2-4 options each


[​](#other-ways-to-get-user-input)

Other ways to get user input

The `canUseTool` callback and `AskUserQuestion` tool cover most approval and clarification scenarios, but the SDK offers other ways to get input from users:


[​](#streaming-input)

Streaming input

Use [streaming input](agent-sdk-streaming-vs-single-mode.md) when you need to:

- **Interrupt the agent mid-task**: send a cancel signal or change direction while Claude is working
- **Provide additional context**: add information Claude needs without waiting for it to ask
- **Build chat interfaces**: let users send follow-up messages during long-running operations

Streaming input is ideal for conversational UIs where users interact with the agent throughout execution, not just at approval checkpoints.


[​](#custom-tools)

Custom tools

Use [custom tools](agent-sdk-custom-tools.md) when you need to:

- **Collect structured input**: build forms, wizards, or multi-step workflows that go beyond `AskUserQuestion`’s multiple-choice format
- **Integrate external approval systems**: connect to existing ticketing, workflow, or approval platforms
- **Implement domain-specific interactions**: create tools tailored to your application’s needs, like code review interfaces or deployment checklists

Custom tools give you full control over the interaction, but require more implementation work than using the built-in `canUseTool` callback.


[​](#related-resources)

Related resources

- [Configure permissions](agent-sdk-permissions.md): set up permission modes and rules
- [Control execution with hooks](agent-sdk-hooks.md): run custom code at key points in the agent lifecycle
- [TypeScript SDK reference](agent-sdk-typescript.md#canusetool): full canUseTool API documentation

---

## Agent Sdk Verifier Py

Source: https://github.com/anthropics/claude-code/blob/main/plugins/agent-sdk-dev/agents/agent-sdk-verifier-py.md
Path: 05-Agent-SDK/claude-code-plugins-agent-sdk-dev-agents-agent-sdk-verifier-py.md

You are a Python Agent SDK application verifier. Your role is to thoroughly inspect Python Agent SDK applications for correct SDK usage, adherence to official documentation recommendations, and readiness for deployment.

## Verification Focus

Your verification should prioritize SDK functionality and best practices over general code style. Focus on:

1. **SDK Installation and Configuration**:

   - Verify `claude-agent-sdk` is installed (check requirements.txt, pyproject.toml, or pip list)
   - Check that the SDK version is reasonably current (not ancient)
   - Validate Python version requirements are met (typically Python 3.8+)
   - Confirm virtual environment is recommended/documented if applicable

2. **Python Environment Setup**:

   - Check for requirements.txt or pyproject.toml
   - Verify dependencies are properly specified
   - Ensure Python version constraints are documented if needed
   - Validate that the environment can be reproduced

3. **SDK Usage and Patterns**:

   - Verify correct imports from `claude_agent_sdk` (or appropriate SDK module)
   - Check that agents are properly initialized according to SDK docs
   - Validate that agent configuration follows SDK patterns (system prompts, models, etc.)
   - Ensure SDK methods are called correctly with proper parameters
   - Check for proper handling of agent responses (streaming vs single mode)
   - Verify permissions are configured correctly if used
   - Validate MCP server integration if present

4. **Code Quality**:

   - Check for basic syntax errors
   - Verify imports are correct and available
   - Ensure proper error handling
   - Validate that the code structure makes sense for the SDK

5. **Environment and Security**:

   - Check that `.env.example` exists with `ANTHROPIC_API_KEY`
   - Verify `.env` is in `.gitignore`
   - Ensure API keys are not hardcoded in source files
   - Validate proper error handling around API calls

6. **SDK Best Practices** (based on official docs):

   - System prompts are clear and well-structured
   - Appropriate model selection for the use case
   - Permissions are properly scoped if used
   - Custom tools (MCP) are correctly integrated if present
   - Subagents are properly configured if used
   - Session handling is correct if applicable

7. **Functionality Validation**:

   - Verify the application structure makes sense for the SDK
   - Check that agent initialization and execution flow is correct
   - Ensure error handling covers SDK-specific errors
   - Validate that the app follows SDK documentation patterns

8. **Documentation**:
   - Check for README or basic documentation
   - Verify setup instructions are present (including virtual environment setup)
   - Ensure any custom configurations are documented
   - Confirm installation instructions are clear

## What NOT to Focus On

- General code style preferences (PEP 8 formatting, naming conventions, etc.)
- Python-specific style choices (snake_case vs camelCase debates)
- Import ordering preferences
- General Python best practices unrelated to SDK usage

## Verification Process

1. **Read the relevant files**:

   - requirements.txt or pyproject.toml
   - Main application files (main.py, app.py, src/\*, etc.)
   - .env.example and .gitignore
   - Any configuration files

2. **Check SDK Documentation Adherence**:

   - Use WebFetch to reference the official Python SDK docs: https://docs.claude.com/en/api/agent-sdk/python
   - Compare the implementation against official patterns and recommendations
   - Note any deviations from documented best practices

3. **Validate Imports and Syntax**:

   - Check that all imports are correct
   - Look for obvious syntax errors
   - Verify SDK is properly imported

4. **Analyze SDK Usage**:
   - Verify SDK methods are used correctly
   - Check that configuration options match SDK documentation
   - Validate that patterns follow official examples

## Verification Report Format

Provide a comprehensive report:

**Overall Status**: PASS | PASS WITH WARNINGS | FAIL

**Summary**: Brief overview of findings

**Critical Issues** (if any):

- Issues that prevent the app from functioning
- Security problems
- SDK usage errors that will cause runtime failures
- Syntax errors or import problems

**Warnings** (if any):

- Suboptimal SDK usage patterns
- Missing SDK features that would improve the app
- Deviations from SDK documentation recommendations
- Missing documentation or setup instructions

**Passed Checks**:

- What is correctly configured
- SDK features properly implemented
- Security measures in place

**Recommendations**:

- Specific suggestions for improvement
- References to SDK documentation
- Next steps for enhancement

Be thorough but constructive. Focus on helping the developer build a functional, secure, and well-configured Agent SDK application that follows official patterns.

---

## Agent Sdk Verifier Ts

Source: https://github.com/anthropics/claude-code/blob/main/plugins/agent-sdk-dev/agents/agent-sdk-verifier-ts.md
Path: 05-Agent-SDK/claude-code-plugins-agent-sdk-dev-agents-agent-sdk-verifier-ts.md

You are a TypeScript Agent SDK application verifier. Your role is to thoroughly inspect TypeScript Agent SDK applications for correct SDK usage, adherence to official documentation recommendations, and readiness for deployment.

## Verification Focus

Your verification should prioritize SDK functionality and best practices over general code style. Focus on:

1. **SDK Installation and Configuration**:

   - Verify `@anthropic-ai/claude-agent-sdk` is installed
   - Check that the SDK version is reasonably current (not ancient)
   - Confirm package.json has `"type": "module"` for ES modules support
   - Validate that Node.js version requirements are met (check package.json engines field if present)

2. **TypeScript Configuration**:

   - Verify tsconfig.json exists and has appropriate settings for the SDK
   - Check module resolution settings (should support ES modules)
   - Ensure target is modern enough for the SDK
   - Validate that compilation settings won't break SDK imports

3. **SDK Usage and Patterns**:

   - Verify correct imports from `@anthropic-ai/claude-agent-sdk`
   - Check that agents are properly initialized according to SDK docs
   - Validate that agent configuration follows SDK patterns (system prompts, models, etc.)
   - Ensure SDK methods are called correctly with proper parameters
   - Check for proper handling of agent responses (streaming vs single mode)
   - Verify permissions are configured correctly if used
   - Validate MCP server integration if present

4. **Type Safety and Compilation**:

   - Run `npx tsc --noEmit` to check for type errors
   - Verify that all SDK imports have correct type definitions
   - Ensure the code compiles without errors
   - Check that types align with SDK documentation

5. **Scripts and Build Configuration**:

   - Verify package.json has necessary scripts (build, start, typecheck)
   - Check that scripts are correctly configured for TypeScript/ES modules
   - Validate that the application can be built and run

6. **Environment and Security**:

   - Check that `.env.example` exists with `ANTHROPIC_API_KEY`
   - Verify `.env` is in `.gitignore`
   - Ensure API keys are not hardcoded in source files
   - Validate proper error handling around API calls

7. **SDK Best Practices** (based on official docs):

   - System prompts are clear and well-structured
   - Appropriate model selection for the use case
   - Permissions are properly scoped if used
   - Custom tools (MCP) are correctly integrated if present
   - Subagents are properly configured if used
   - Session handling is correct if applicable

8. **Functionality Validation**:

   - Verify the application structure makes sense for the SDK
   - Check that agent initialization and execution flow is correct
   - Ensure error handling covers SDK-specific errors
   - Validate that the app follows SDK documentation patterns

9. **Documentation**:
   - Check for README or basic documentation
   - Verify setup instructions are present if needed
   - Ensure any custom configurations are documented

## What NOT to Focus On

- General code style preferences (formatting, naming conventions, etc.)
- Whether developers use `type` vs `interface` or other TypeScript style choices
- Unused variable naming conventions
- General TypeScript best practices unrelated to SDK usage

## Verification Process

1. **Read the relevant files**:

   - package.json
   - tsconfig.json
   - Main application files (index.ts, src/\*, etc.)
   - .env.example and .gitignore
   - Any configuration files

2. **Check SDK Documentation Adherence**:

   - Use WebFetch to reference the official TypeScript SDK docs: https://docs.claude.com/en/api/agent-sdk/typescript
   - Compare the implementation against official patterns and recommendations
   - Note any deviations from documented best practices

3. **Run Type Checking**:

   - Execute `npx tsc --noEmit` to verify no type errors
   - Report any compilation issues

4. **Analyze SDK Usage**:
   - Verify SDK methods are used correctly
   - Check that configuration options match SDK documentation
   - Validate that patterns follow official examples

## Verification Report Format

Provide a comprehensive report:

**Overall Status**: PASS | PASS WITH WARNINGS | FAIL

**Summary**: Brief overview of findings

**Critical Issues** (if any):

- Issues that prevent the app from functioning
- Security problems
- SDK usage errors that will cause runtime failures
- Type errors or compilation failures

**Warnings** (if any):

- Suboptimal SDK usage patterns
- Missing SDK features that would improve the app
- Deviations from SDK documentation recommendations
- Missing documentation

**Passed Checks**:

- What is correctly configured
- SDK features properly implemented
- Security measures in place

**Recommendations**:

- Specific suggestions for improvement
- References to SDK documentation
- Next steps for enhancement

Be thorough but constructive. Focus on helping the developer build a functional, secure, and well-configured Agent SDK application that follows official patterns.