Skip to content

Python SDK reference

Every function, class, option, message type, hook type, error and built-in tool schema in the claude-agent-sdk Python package, with notes on the traps.

This is the full reference for the claude-agent-sdk Python package. It is organised so you can jump to what you need: functions first, then the client class, then options, messages, errors, hooks, tool schemas and sandbox settings. Task-focused guides live on the other Agent SDK pages and are linked throughout.

Note: Fragments on this page that use async for or await at top level are illustrative. Wrap them in async def main(): ... and call asyncio.run(main()) to run them.

Installing

System Python on recent Debian, Ubuntu and Homebrew installs refuses pip install with error: externally-managed-environment, so use a virtual environment:

python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk

The quickstart covers uv, Windows PowerShell and API key setup.

query() or ClaudeSDKClient?

query()ClaudeSDKClient
SessionNew one per call unless you resumeOne session across many exchanges
ConversationOne exchangeMany, with shared context
ConnectionHandled for youYou connect and disconnect
Streaming inputYesYes
InterruptsNoYes
Hooks and custom toolsYesYes
Follow-upsVia continue_conversation or resumeAutomatic
Best forOne-off jobs, batch scriptsChat UIs, flows where the next step depends on the reply

Functions

query()

async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None,
    transport: Transport | None = None,
) -> AsyncIterator[Message]
ParameterMeaning
promptA string, or an async iterable of user message dicts for streaming input
optionsA ClaudeAgentOptions; None means defaults
transportA custom Transport instead of the default subprocess

Returns an async iterator of Message objects. Each call starts a fresh session unless continue_conversation=True or resume is set. See sessions.

from claude_agent_sdk import query, ClaudeAgentOptions

opts = ClaudeAgentOptions(system_prompt="You are a careful Django reviewer.", permission_mode="plan")
async for msg in query(prompt="Review the views in orders/ for N+1 queries", options=opts):
    print(msg)

tool()

Decorator that turns an async function into an in-process MCP tool.

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]]

The schema can take three forms:

FormExampleNotes
Type mapping{"sku": str, "qty": int}Simplest. All keys required.
JSON Schema dict{"type": "object", "properties": {...}, "required": [...]}For ranges, enums and optional fields
TypedDict classclass Args(TypedDict): ...NotRequired keys are left out of required. On 3.11+ import from typing; on 3.10 import TypedDict and NotRequired from typing_extensions (installed by the SDK there).

In the mapping and TypedDict forms, Annotated[type, "description"] sets a field description.

from typing import Annotated, Any, NotRequired, TypedDict
from claude_agent_sdk import tool

class StockArgs(TypedDict):
    sku: Annotated[str, "Product SKU, e.g. TEA-EB-250"]
    warehouse: NotRequired[Annotated[str, "Warehouse code; defaults to MAN"]]

@tool("check_stock", "Return units on hand for a SKU", StockArgs)
async def check_stock(args: dict[str, Any]) -> dict[str, Any]:
    wh = args.get("warehouse", "MAN")
    units = await inventory.lookup(args["sku"], wh)
    return {"content": [{"type": "text", "text": f"{units} units of {args['sku']} at {wh}"}]}

The handler returns a dict with a content list of MCP content blocks. More in custom tools.

ToolAnnotations

Optional hints passed as annotations. It extends the MCP SDK's mcp.types.ToolAnnotations with maxResultSizeChars. You can write hints in camelCase or snake_case (readOnlyHint=True or read_only_hint=True). When reading back, use the spelling your installed mcp package declares (.readOnlyHint on mcp 1.x, .read_only_hint on 2.x); .maxResultSizeChars works on both. A plain mcp.types.ToolAnnotations is accepted too.

FieldDefaultMeaning
titleNoneHuman-readable title
readOnlyHintFalseTool does not change its environment
destructiveHintTrueMay make destructive changes (only meaningful if not read-only)
idempotentHintFalseRepeating the same call has no further effect (only meaningful if not read-only)
openWorldHintTrueTalks to external systems; False for a closed domain such as a memory store
maxResultSizeCharsNoneCharacters of text result kept inline before Claude Code spills it to a file, up to 500,000. Image results unaffected. Sent in the tool's _meta as anthropic/maxResultSizeChars.

Snake_case names and the typed maxResultSizeChars need SDK 0.2.140+. Versions 0.1.31 to 0.2.139 re-export the MCP class unchanged; from 0.1.55 you can still pass maxResultSizeChars as a keyword and it is forwarded. Hints are advisory and must not be relied on for security.

create_sdk_mcp_server()

def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None,
) -> McpSdkServerConfig

Bundles decorated tools into a server that runs inside your process. Register it in mcp_servers; tool names become mcp__<key>__<tool>.

from claude_agent_sdk import create_sdk_mcp_server, ClaudeAgentOptions

warehouse = create_sdk_mcp_server("warehouse", "1.2.0", tools=[check_stock])
opts = ClaudeAgentOptions(
    mcp_servers={"wh": warehouse},
    allowed_tools=["mcp__wh__check_stock"],
)

Session helpers

These are synchronous and read transcripts from disk.

FunctionSignatureReturns
list_sessions(directory=None, limit=None, offset=0, include_worktrees=True)list[SDKSessionInfo], newest first
get_session_messages(session_id, directory=None, limit=None, offset=0)list[SessionMessage]
get_session_info(session_id, directory=None)SDKSessionInfo or None
rename_session(session_id, title, directory=None)None
tag_session(session_id, tag, directory=None)None

Notes:

  • Omitting directory searches every project.
  • include_worktrees includes sessions from all worktrees when directory is inside a git repo.
  • rename_session and tag_session append entries, so repeat calls are safe and the latest wins. Pass tag=None to clear. Both raise ValueError for a non-UUID ID or an empty title or tag (tags are Unicode-sanitised first) and FileNotFoundError if the session is missing.

SDKSessionInfo

FieldTypeMeaning
session_idstrSession ID
summarystrDisplay title: custom title, latest prompt, generated summary or first prompt
last_modifiedintMilliseconds since epoch
file_sizeint | NoneBytes; None for remote stores
custom_titlestr | NoneUser-set or generated title
first_promptstr | NoneFirst meaningful prompt
git_branchstr | NoneBranch at the end of the session
cwdstr | NoneWorking directory
tagstr | NoneTag from tag_session()
created_atint | NoneMilliseconds since epoch

SessionMessage

FieldTypeMeaning
type"user" or "assistant"Role
uuidstrMessage ID
session_idstrSession ID
messageAnyRaw content
parent_tool_use_idstr | NoneFor subagent messages, the spawning Agent tool-use ID
parent_agent_idstr | NoneFor nested subagents, the parent subagent's ID (SDK 0.2.140+)
from claude_agent_sdk import list_sessions, tag_session

for s in list_sessions(directory="/srv/repos/storefront", limit=20):
    if "refund" in (s.first_prompt or "").lower():
        tag_session(s.session_id, "billing")

ClaudeSDKClient

Holds one conversation open across many exchanges. It is what you reach for in chat interfaces.

MethodWhat it does
__init__(options=None, transport=None)Configure
connect(prompt=None)Open the connection, optionally with a first prompt or message stream
query(prompt, session_id="default")Send a new message (string or async iterable)
receive_messages()Every message, indefinitely
receive_response()Messages up to and including the next ResultMessage
interrupt()Stop the current turn
set_permission_mode(mode)Change permission mode mid-session
set_model(model)Change model; None resets to the default
rewind_files(user_message_id)Restore files to a checkpoint; needs enable_file_checkpointing=True (see file checkpointing)
get_mcp_status()McpStatusResponse for every MCP server
get_context_usage()ContextUsageResponse, the data behind /context
reconnect_mcp_server(name)Retry a failed or dropped MCP server
toggle_mcp_server(name, enabled)Enable or disable a server; disabling removes its tools
stop_task(task_id)Stop a background task; a TaskNotificationMessage with status stopped follows
get_server_info()Initialisation info, including commands and output styles
disconnect()Close

Use it as an async context manager to connect and disconnect automatically:

from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock

async with ClaudeSDKClient() as client:
    await client.query("Which module in this repo handles VAT?")
    async for m in client.receive_response():
        if isinstance(m, AssistantMessage):
            print("".join(b.text for b in m.content if isinstance(b, TextBlock)))

    await client.query("Does it handle the reverse charge for EU B2B sales?")
    async for m in client.receive_response():
        ...

Warning: Avoid break inside these loops: it can cause asyncio clean-up problems. Let the loop finish, or set a flag.

Streaming input with the client

client.query() accepts an async iterable of user message dicts, useful for building content at send time or attaching images. Claude starts replying to the first yielded message straight away, and receive_response() stops at the result for that reply, so put everything Claude needs into one message and pair each query() with its own receive_response() loop.

async def build_prompt():
    readings = await sensors.latest()
    yield {"type": "user", "message": {"role": "user",
           "content": f"Spot anomalies in these readings: {readings}"}}

async with ClaudeSDKClient() as client:
    await client.query(build_prompt())
    async for m in client.receive_response():
        ...

Interrupting

async with ClaudeSDKClient(ClaudeAgentOptions(allowed_tools=["Bash"])) as client:
    await client.query("Run the full integration suite")
    await asyncio.sleep(5)
    await client.interrupt()

    async for m in client.receive_response():      # drain the interrupted turn
        if isinstance(m, ResultMessage):
            print(m.terminal_reason)               # "aborted_streaming" or "aborted_tools"

    await client.query("Just run tests/unit instead")
    async for m in client.receive_response():
        ...

interrupt() does not clear the buffer. The interrupted turn's messages, including its ResultMessage, are still waiting. Drain them before reading the next reply, or you will read the old turn's output by mistake.

Custom permission logic

from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, ToolPermissionContext

async def gatekeeper(tool_name: str, data: dict, ctx: ToolPermissionContext):
    path = data.get("file_path", "")
    if tool_name in ("Write", "Edit") and path.startswith("/srv/releases/"):
        return PermissionResultDeny(message="Release artefacts are read-only", interrupt=True)
    if tool_name == "Write" and path.endswith(".env"):
        return PermissionResultAllow(updated_input={**data, "file_path": path + ".example"})
    return PermissionResultAllow(updated_input=data)

opts = ClaudeAgentOptions(can_use_tool=gatekeeper)

Do not also list gated tools in allowed_tools: allow rules approve calls before can_use_tool is ever consulted. See permissions.

Types: dataclass or TypedDict?

The SDK uses both, and it matters at runtime:

  • Dataclasses (ResultMessage, AgentDefinition, TextBlock, ...) are objects. Use attributes: msg.result.
  • TypedDicts (ThinkingConfigEnabled, McpStdioServerConfig, SyncHookJSONOutput, ...) are plain dicts. Use keys: cfg["budget_tokens"].

ClassName(field=value) works for both, but only dataclasses give you attributes.

ClaudeAgentOptions

Tools and permissions

OptionTypeDefaultMeaning
toolslist[str] | ToolsPreset | NoneNoneBuilt-in tool set. {"type": "preset", "preset": "claude_code"} for the full default set
allowed_toolslist[str][]Auto-approve these. Does not restrict others, which fall through to the mode and can_use_tool. Naming a task-tracking tool opts the session in to those tools.
disallowed_toolslist[str][]Bare name removes the tool. Scoped rule such as "Bash(rm *)" denies matching calls in every mode, including bypassPermissions, matched as written
permission_modePermissionMode | NoneNoneStarting mode
can_use_toolCanUseTool | NoneNoneCallback when the permission flow reaches a prompt. Never called for calls already approved
permission_prompt_tool_namestr | NoneNoneMCP tool to use for permission prompts
sandboxSandboxSettings | NoneNoneProgrammatic sandbox config (see Sandbox)

Prompt, model and reasoning

OptionTypeDefaultMeaning
system_promptstr | SystemPromptPreset | SystemPromptCustom | SystemPromptFile | NoneNoneSee system prompt types
modelstr | NoneNoneAlias or full model ID (see model configuration)
fallback_modelstr | NoneNoneUsed if the primary fails; accepts a comma-separated list
thinkingThinkingConfig | NoneNoneExtended thinking; overrides max_thinking_tokens
max_thinking_tokensint | NoneNoneDeprecated; use thinking
effortEffortLevel | NoneNoneReasoning depth
betaslist[SdkBeta][]Beta features
output_formatdict | NoneNone{"type": "json_schema", "schema": {...}} for structured outputs
task_budgetTaskBudget | NoneNoneAPI-side token budget {"total": int}, sent as output_config.task_budget with the task-budgets-2026-03-13 beta header

Limits

OptionTypeDefaultMeaning
max_turnsint | NoneNoneCap on tool-use round trips
max_budget_usdfloat | NoneNoneStop when the client-side cost estimate reaches this. Counts this call only; restored session totals excluded. See cost tracking

Sessions

OptionTypeDefaultMeaning
continue_conversationboolFalseContinue the most recent conversation
resumestr | NoneNoneSession ID to resume
session_idstr | NoneNoneUse this UUID instead of a generated one. Cannot combine with continue_conversation or resume unless fork_session is set
fork_sessionboolFalseWhen resuming, branch to a new session ID
resume_session_atstr | NoneNoneLoad only up to and including this message UUID. Use with resume, usually with fork_session (SDK 0.2.137+)
resume_drops_turnstr | NoneNoneUUID of the prompt whose turn the truncation discards; the CLI refuses if the discarded range contains other entries (SDK 0.2.137+, Claude Code 2.1.223+)
enable_file_checkpointingboolFalseTrack edits for rewinding
session_storeSessionStore | NoneNoneMirror transcripts to external storage (see session storage)
session_store_flush"batched" or "eager""batched"Batched flushes once per turn or when the buffer fills; eager starts a background flush after every frame
load_timeout_msint60000Timeout for session_store.load() and list_subkeys() while resuming

Environment and process

OptionTypeDefaultMeaning
cwdstr | Path | NoneNoneWorking directory
add_dirslist[str | Path][]Extra directories, passed as --add-dir. With the project source, their skills, commands and subagents load too
envdict[str, str]{}Merged over the inherited environment. Set CLAUDE_AGENT_SDK_CLIENT_APP to name your app in the User-Agent
cli_pathstr | Path | NoneNoneUse a specific Claude Code executable
extra_argsdict[str, str | None]{}Extra CLI flags; None for a bare flag
userstr | NoneNonePOSIX only: OS user to run the subprocess as. Environment, including HOME, is kept
max_buffer_sizeint | NoneNoneMax bytes buffered from CLI stdout
stderrCallable[[str], None] | NoneNoneReceives CLI stderr lines
debug_stderrAnysys.stderrDeprecated and ignored

Configuration sources and extensions

OptionTypeDefaultMeaning
setting_sourceslist[SettingSource] | NoneNoneWhich settings files load. [] disables user, project and local. If unset and skills is set, only user and project load
settingsstr | NoneNoneSettings file path or inline JSON string
mcp_serversdict | str | Path{}Server configs or a path to a config file
strict_mcp_configboolFalseUse only mcp_servers, ignoring .mcp.json, user settings, plugin servers and claude.ai connectors (--strict-mcp-config)
agentsdict[str, AgentDefinition] | NoneNoneProgrammatic subagents
skillslist[str] | "all" | NoneNoneSkills Claude may invoke. Adds Skill to allowed_tools; include "Skill" if you pass tools. Bad names raise ValueError (SDK 0.2.129+)
pluginslist[SdkPluginConfig][]Local plugins
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneHook callbacks

Stream shape

OptionTypeDefaultMeaning
include_partial_messagesboolFalseYield StreamEvents
include_hook_eventsboolFalseYield hook lifecycle HookEventMessages
forward_subagent_textboolFalseInclude text and thinking from foreground subagents (SDK 0.2.140+)
verbatim_promptsboolFalseSend every user message with client_composed=True, so Claude Code delivers prompts as written. Overrides per-message values while on (SDK 0.2.158+, Claude Code 2.1.248+)

Timeouts for slow APIs

Pass these through env:

VariableDefaultEffect
API_TIMEOUT_MS600000Per-request timeout, main loop and subagents
CLAUDE_CODE_MAX_RETRIES10 (max 15)Retries, each with its own timeout window. CLAUDE_CODE_RETRY_WATCHDOG=1 retries capacity errors indefinitely and, from v2.1.199, raises the default for other transient errors to 300 and lifts the cap
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSCLAUDE_STREAM_IDLE_TIMEOUT_MS + 5 min while the stream watchdog is on, otherwise 600000Aborts a subagent that stops producing stream events and reports it to the parent; background subagents are marked failed with any partial result (before v2.1.257, always 600000)
CLAUDE_ENABLE_STREAM_WATCHDOGonSet 0 to disable the watchdog that aborts a response whose body stops streaming
CLAUDE_STREAM_IDLE_TIMEOUT_MS300000 (minimum)Watchdog idle limit

With include_partial_messages on, a gateway that holds a response open with keep-alives produces ping StreamEvents; treat them as signs of life (before v2.1.257 they stopped after five minutes).

Settings sources

SettingSource = Literal["user", "project", "local"]
ValueFile
"user"~/.claude/settings.json
"project".claude/settings.json
"local".claude/settings.local.json

When setting_sources is unset and skills is unset, query() loads all three, like the CLI. Endpoint-managed policy always loads; server-managed settings are fetched when authenticating with an organisation credential on an eligible configuration. Claude Code features in the SDK lists inputs that load regardless.

Precedence, highest first: local, project, user. Programmatic options (agents, allowed_tools, settings) beat all three; managed policy beats programmatic options.

CLAUDE.md loads with the project source. setting_sources=[] is the right choice for SDK-only apps that define everything in code.

Note: In Python SDK 0.1.59 and earlier, an empty list behaved like None and did not disable anything. Upgrade if you rely on [].

System prompt types

TypeShapeNotes
plain string"You are..."Custom prompt
SystemPromptPreset{"type": "preset", "preset": "claude_code", "append"?: str, "exclude_dynamic_sections"?: bool, "snapshot"?: bool}Claude Code's prompt plus your text
SystemPromptCustom{"type": "custom", "prompt": str, "snapshot"?: bool}Same as a string but can set snapshot (SDK 0.2.153+)
SystemPromptFile{"type": "file", "path": str}Maps to --system-prompt-file
  • exclude_dynamic_sections moves per-user context such as the auto memory location into the first user message so the prompt caches across users.
  • snapshot: False rebuilds the prompt every request instead of reusing the one recorded on the first request (SDK 0.2.153+).
  • String and custom prompts travel as a command-line argument. On Linux a single argument over roughly 128 KB fails with Argument list too long; on Windows the whole command line is capped near 32 KB. Use the file form for big prompts.

Details in modifying system prompts.

AgentDefinition

@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
FieldMeaning
descriptionWhen to use this agent (required)
promptIts system prompt (required)
toolsAllowed tools; omit to inherit all subagent tools
disallowedToolsTools to remove; accepts mcp__server, mcp__server__*, mcp__*
model"sonnet", "opus", "haiku", "inherit" or a full ID
skillsSkills preloaded at start
memory"user", "project" or "local"
mcpServersServer names or inline {name: config} dicts
initialPromptFirst user turn when this agent is the main thread agent
maxTurnsTurn cap
backgroundAlways run in the background
effortNamed level or integer
permissionModeMode inside this agent, subject to inheritance rules

Warning: These field names are camelCase to match the wire format, unlike the snake_case ClaudeAgentOptions. Passing max_turns= raises a TypeError. There is no omitClaudeMd field in Python; that is TypeScript-only. See subagents.

Enumerations

PermissionMode = Literal["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions", "auto"]
EffortLevel = Literal["low", "medium", "high", "xhigh", "max"]   # xhigh falls back to high where unsupported
SdkBeta = Literal["context-1m-2025-08-07"]

Warning: On the Claude API the context-1m-2025-08-07 beta is retired for Sonnet 4.5 and Sonnet 4; passing it with those models makes over-200K requests fail. For a 1M window, use a model that has it by default, such as claude-sonnet-5-5 or claude-opus-5-5, or append [1m] to a model that offers a 1M variant, such as claude-opus-4-6[1m].

ThinkingConfig

Three TypedDict variants (plain dicts at runtime):

VariantKeysEffect
ThinkingConfigAdaptivetype="adaptive", display?Claude decides when to think
ThinkingConfigEnabledtype="enabled", budget_tokens, display?Thinking with a token budget
ThinkingConfigDisabledtype="disabled"Off

display is "summarized" or "omitted". On Opus 4.7 and later the API defaults to "omitted", so set "summarized" to get text in ThinkingBlocks. Some providers (Amazon Bedrock, Google Cloud's Agent Platform) do not receive your display, so Opus 4.7+ thinking blocks come back empty there regardless.

ClaudeAgentOptions(thinking={"type": "enabled", "budget_tokens": 16000, "display": "summarized"})

Other small types

TypeShape
ToolsPreset{"type": "preset", "preset": "claude_code"}
TaskBudget{"total": int}
OutputFormat{"type": "json_schema", "schema": {...}}
SdkPluginConfig{"type": "local", "path": str}

Permission callback types

CanUseTool = Callable[[str, dict[str, Any], ToolPermissionContext], Awaitable[PermissionResult]]
PermissionResult = PermissionResultAllow | PermissionResultDeny

The callback stands in for the interactive prompt and runs only when the evaluation flow reaches one. For a check on every call, use a PreToolUse hook.

ToolPermissionContext

FieldMeaning
signalReserved
suggestionslist[PermissionUpdate] from the CLI. Bash prompts include one with destination localSettings; returning it in updated_permissions persists the rule to .claude/settings.local.json
tool_use_idThe call this prompt is for; always set
agent_idSubagent ID, or None for the main agent
blocked_pathPath that triggered the prompt, when relevant
decision_reasonWhy the prompt happened; carries a PreToolUse hook's reason when it returned ask
titleFull prompt sentence, e.g. Claude wants to read foo.txt
display_nameShort action label for buttons, e.g. Read file
descriptionSubtitle for your UI

PermissionResultAllow: behavior="allow", updated_input: dict | None, updated_permissions: list[PermissionUpdate] | None.

PermissionResultDeny: behavior="deny", message: str = "", interrupt: bool = False (stop the whole turn).

PermissionUpdate

FieldValues
typeaddRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories
ruleslist[PermissionRuleValue] (each tool_name, optional rule_content)
behaviorallow, deny, ask
modeA PermissionMode for setMode
directoriesFor directory operations
destinationuserSettings, projectSettings, localSettings, session

MCP configuration types

McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig
TypeKeys
McpStdioServerConfigtype?: "stdio" (optional for backwards compatibility), command, args?, env?
McpSSEServerConfigtype: "sse", url, headers?
McpHttpServerConfigtype: "http", url, headers?
McpSdkServerConfigtype: "sdk", name, instance

Status types

get_mcp_status() returns McpStatusResponse: {"mcpServers": list[McpServerStatus]}.

McpServerStatus keyMeaning
nameServer name
statusconnected, failed, needs-auth, pending or disabled
serverInfo{"name", "version"}
errorFailure reason
configMcpServerStatusConfig: any transport config, plus McpSdkServerConfigStatus (type="sdk", name, no instance) and McpClaudeAIProxyServerConfig (type="claudeai-proxy", url, id)
scopeConfiguration scope
toolsEach with name, description, annotations

ContextUsageResponse

What get_context_usage() returns: the same payload as /context, including display fields such as color and gridRows. Building it makes several token-counting API requests that do not appear in the stream (not billed on the Anthropic API).

KeyMeaning
categoriesList of {name, tokens, color, isDeferred?}
totalTokensCurrent context use
maxTokens / rawMaxTokensThe window measured against (model window, or the lower auto-compact window); both carry the same value
percentageUse as a percentage
modelModel
isAutoCompactEnabled, autoCompactThreshold?Auto-compact state
memoryFiles, mcpTools, agents, gridRowsBreakdown lists
slashCommands?, skills?, messageBreakdown?Further breakdowns
apiUsage?Usage from the latest API response, not a session total

deferredBuiltinTools, systemTools and systemPromptSections are declared but left unset.

Messages

Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEvent | RateLimitEvent | ConversationResetMessage

UserMessage

FieldMeaning
contentString or content blocks
uuidMessage ID (needs replay-user-messages for checkpoint UUIDs)
parent_tool_use_idSet when the message is a tool result
tool_use_resultThe tool's structured output (see Built-in tool schemas)
originProvenance on injected turns such as task notifications and peer messages (SDK 0.2.137+)

For results from an external MCP server that contain resource_link blocks, tool_use_result has a resourceLinks list (SDK 0.2.150+, Claude Code 2.1.257+). Claude sees each link as a line of text; read resourceLinks to render them. Absent when there are no links and on subagent results; capped at 50 links or 64 KiB of JSON. In-process tool() tools never produce it, because their links are flattened to text.

AssistantMessage

FieldMeaning
contentList of content blocks
modelModel that responded
parent_tool_use_idSet inside a subagent
errorAssistantMessageError if the response failed
usagePer-message usage; output_tokens is a placeholder
message_idAPI message ID, shared by messages from the same response
stop_reasone.g. end_turn, tool_use
session_id, uuidIdentifiers

AssistantMessageError lists authentication_failed, billing_error, rate_limit, invalid_request, server_error and unknown, but the CLI can emit others such as max_output_tokens. Treat unlisted values like unknown.

SystemMessage

subtype: str and data: dict. Subtypes without their own class arrive as this. With CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1, read data["state"] on session_state_changed messages. Those can arrive after a result, so use receive_messages() rather than receive_response() to see them.

ResultMessage

The final message of a query() (or of each turn in streaming input mode).

FieldMeaning
subtypesuccess, error_during_execution, error_max_turns, error_max_budget_usd, error_max_structured_output_retries
duration_ms, duration_api_msWall and API time
is_errorAlways True for error_*; True on success when the final request failed
num_turnsTurns used
session_idSession
stop_reasonAPI stop reason
total_cost_usdClient-side estimate, optional
usageMain loop only, per turn in streaming mode
resultFinal text on success, None otherwise; may hold an API error string when is_error
structured_outputParsed output when output_format is set
model_usagePer-model usage including subagents and internal calls
permission_denialsCalls that were denied
deferred_tool_useSet when a hook deferred a call
errorsLoop-level errors on error_*
api_error_statusHTTP status of a terminating API error, on success only
uuidID
terminal_reasoncompleted, max_turns, api_error, aborted_streaming, aborted_tools, etc. None on old CLIs, local command results and synthesised fatal errors
originOrigin of the triggering prompt; None or {"kind": "human"} for your own (SDK 0.2.137+)

Fields that do not apply to a subtype are None.

usage keys: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens. Subagents are excluded, so prefer model_usage.

model_usage covers the main loop, subagents, compaction and Workflow agents; helper calls outside that pipeline (the permission classifier, token counting) are excluded. It is cumulative across turns in streaming mode and includes restored totals on resume. Each value is a ModelUsage (from claude_agent_sdk.types import ModelUsage) with camelCase keys:

KeyMeaning
inputTokens, outputTokensTokens
cacheReadInputTokens, cacheCreationInputTokensCache tokens
webSearchRequestsWeb searches
thinkingTokensAlready included in outputTokens; not declared, read with .get() (SDK 0.2.150+)
costUSDEstimate
contextWindow, maxOutputTokensModel limits
canonicalModelID used for pricing; not always present
providerfirstParty, bedrock, vertex, foundry, anthropicAws, mantle or gateway; not always present
costBasislist, managed or unknown; read with .get() (Claude Code 2.1.246+)

StreamEvent

Only with include_partial_messages=True. Import from claude_agent_sdk.types. Fields: uuid, session_id, event (the raw API stream event) and parent_tool_use_id, which is always None because stream events are main-session only.

RateLimitEvent and RateLimitInfo

Emitted when rate-limit status changes. rate_limit_info carries:

FieldMeaning
statusallowed, allowed_warning (getting close) or rejected
resets_atUnix time the window resets
rate_limit_typefive_hour, seven_day, seven_day_opus, seven_day_sonnet, overage
utilization0.0 to 1.0
overage_status, overage_resets_at, overage_disabled_reasonPay-as-you-go state
rawThe full dict from the CLI

ConversationResetMessage

Emitted when the conversation is replaced without disconnecting, for example after /clear (SDK 0.2.137+). Fields: new_conversation_id (opaque, not the next session_id), uuid, and session_id (the session that was reset). Messages afterwards carry a new session_id.

Background task messages

A background task is a backgrounded Bash command, a Monitor watch, a subagent or a remote agent. (Nothing to do with the old Task tool name.)

MessageKey fields
TaskStartedMessagetask_id, description, tool_use_id, task_type (local_bash, local_agent, remote_agent)
TaskProgressMessagetask_id, description, usage: TaskUsage, last_tool_name
TaskNotificationMessagetask_id, status (completed, failed, stopped), output_file, summary, usage

All three subclass SystemMessage and include uuid and session_id. TaskUsage is {"total_tokens", "tool_uses", "duration_ms"}.

When the CLI backgrounds a long MCP tool call, the tool result holds a placeholder and the real result comes in the completed notification, with a resource_links list in message.data (SDK 0.2.150+, Claude Code 2.1.257+). Match it to the call by tool_use_id.

Content blocks

ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | ServerToolUseBlock | ServerToolResultBlock
BlockFields
TextBlocktext
ThinkingBlockthinking, signature
ToolUseBlockid, name, input
ToolResultBlocktool_use_id, content, is_error

Errors

ExceptionParentWhen
ClaudeSDKErrorExceptionBase for everything
CLIConnectionErrorClaudeSDKErrorCould not connect to Claude Code
CLINotFoundErrorCLIConnectionErrorClaude Code not installed or not found; has cli_path
ProcessErrorClaudeSDKErrorThe process failed; has exit_code, stderr
ResultErrorProcessErrorThe run ended with an error result (SDK 0.2.140+)
CLIJSONDecodeErrorClaudeSDKErrorUnparseable output; has line, original_error

ResultError attributes: subtype, errors (list, possibly empty), result, api_error_status, terminal_reason, session_id, data (raw payload). Check terminal_reason before subtype: a failed final request such as an API error comes through with subtype="success" and terminal_reason="api_error", while your own limits produce error_* subtypes.

from claude_agent_sdk import query, CLINotFoundError, ResultError, ProcessError, CLIJSONDecodeError

try:
    async for m in query(prompt="Tidy the README"):
        ...
except CLINotFoundError:
    print("Claude Code missing: pip install --force-reinstall claude-agent-sdk")
except ResultError as e:                 # before ProcessError
    print("Run failed:", e.terminal_reason or e.subtype, e)
except ProcessError as e:
    print("Process exited", e.exit_code)
except CLIJSONDecodeError as e:
    print("Bad output line:", e.line)

The troubleshooting page maps each error message to a fix.

Hook types

Full guide: hooks in the SDK.

HookEvent = Literal["PreToolUse", "PostToolUse", "PostToolUseFailure", "UserPromptSubmit",
                    "Stop", "SubagentStop", "PreCompact", "Notification",
                    "SubagentStart", "PermissionRequest"]

HookCallback = Callable[[HookInput, str | None, HookContext], Awaitable[HookJSONOutput]]

TypeScript supports many more events. SessionStart and SessionEnd are only available to Python as shell hooks in settings files.

HookMatcher: matcher: str | None (tool name or pattern such as "Write|Edit"), hooks: list[HookCallback], timeout: float | None in seconds (defaults per event: 600 for most, 30 for UserPromptSubmit).

HookContext: {"signal": None}, reserved.

Hook inputs

Every input includes session_id, transcript_path, cwd and optionally permission_mode, plus hook_event_name.

Input typeExtra fields
PreToolUseHookInputtool_name, tool_input, tool_use_id, agent_id?, agent_type?
PostToolUseHookInputtool_name, tool_input, tool_response, tool_use_id, agent_id?, agent_type?
PostToolUseFailureHookInputtool_name, tool_input, tool_use_id, error, is_interrupt?, agent_id?, agent_type?
UserPromptSubmitHookInputprompt
StopHookInputstop_hook_active
SubagentStopHookInputstop_hook_active, agent_id, agent_transcript_path, agent_type
PreCompactHookInputtrigger (manual or auto), custom_instructions
NotificationHookInputmessage, title?, notification_type
SubagentStartHookInputagent_id, agent_type
PermissionRequestHookInputtool_name, tool_input, permission_suggestions?, agent_id?, agent_type?

is_interrupt is true when the failure reached Claude Code as an abort rather than a tool-reported error. Cancelling a tool with interrupt() does not fire this hook at all.

Hook outputs

HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput

SyncHookJSONOutput

KeyMeaning
continue_Whether to carry on (default true); sent as continue
suppressOutputHide stdout from the transcript
stopReasonMessage when continue_ is false
decision"block"
systemMessageWarning for the user
reasonFeedback for Claude
hookSpecificOutputEvent-specific fields below

hookSpecificOutput by event (always include hookEventName):

EventFields
PreToolUsepermissionDecision (allow, deny, ask, defer), permissionDecisionReason, updatedInput, additionalContext
PostToolUseadditionalContext, updatedToolOutput, updatedMCPToolOutput (MCP only; prefer updatedToolOutput)
PostToolUseFailureadditionalContext
UserPromptSubmitadditionalContext
NotificationadditionalContext
SubagentStartadditionalContext
PermissionRequestdecision (dict)

AsyncHookJSONOutput: async_: True (sent as async) and optional asyncTimeout in milliseconds.

async def block_force_push(data, tool_use_id, ctx):
    cmd = data["tool_input"].get("command", "")
    if "git push" in cmd and ("--force" in cmd or " -f" in cmd):
        return {"hookSpecificOutput": {"hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": "Force pushes are not allowed from agents"}}
    return {}

opts = ClaudeAgentOptions(hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[block_force_push], timeout=60)]})

Built-in tool schemas

The Python SDK does not export types for these, but this is the shape of tool_use input and of UserMessage.tool_use_result output for each tool. Keys appear exactly as Claude Code emits them; optional keys are omitted when they do not apply.

Agent

Name Agent (Task accepted as an alias; the init message's tools list still says Task).

InputMeaning
description3 to 5 word summary
promptThe task
subagent_typeWhich agent
modelsonnet, opus, haiku or fable
effortlow to max
run_in_backgroundDefaults to background; False to wait
nameName for the spawned agent
isolationworktree or remote
team_name, modeDeprecated and ignored

Output is discriminated on status:

  • completed: agentId, agentType, content (text blocks with optional citations), resolvedModel (Claude Code 2.1.174+), modelsUsed (only set when the model was swapped mid-run), totalToolUseCount, totalDurationMs, totalTokens and usage (both from the final API request, not the whole run), toolStats (readCount, searchCount, bashCount, editFileCount, linesAdded, linesRemoved, otherToolCount, frameCount), prompt, and worktreePath / worktreeBranch if a worktree was kept. usage includes output_tokens_details.thinking_tokens (SDK 0.2.136+) and fallback_credit (SDK 0.2.162+).
  • async_launched: isAsync, agentId, description, resolvedModel (at the backgrounding point), modelsUsed, prompt, outputFile, canReadOutputFile.
  • remote_launched: taskId, sessionUrl, description, prompt, outputFile.

AskUserQuestion

Input: questions (1 to 4), each with question, header (up to 12 characters), options (2 to 4, each label of 1 to 5 words, description, optional preview) and multiSelect. Also answers (filled by the permission system; multi-select answers are comma-joined), annotations (per question, preview and notes) and metadata.

Output: questions, answers (question text to answer), response (a free-form reply instead of answers; Claude then sees The user responded: ...), annotations, and afkTimeoutMs when the dialog auto-resolved after inactivity. See user input.

Bash

Input: command, timeout (ms; foreground capped at 600000 by default; with run_in_background on Claude Code 2.1.285+ it is the background limit, default 1800000, capped at 7200000 unless raised), description, run_in_background.

Output: stdout (stdout and stderr interleaved), stderr (notices from the tool itself), interrupted, isImage, backgroundTaskId.

Monitor

Input: exactly one of command (each stdout line is an event) or ws ({"url", "protocols"}; each text frame is an event; Claude Code 2.1.195+), plus description and timeout_ms (default 300000, max 3600000, effective at most 1800000). Commands follow Bash permissions; WebSocket watches prompt separately.

Output: taskId, timeoutMs, persistent (false: every watch has a deadline).

Edit

Input: file_path, old_string, new_string, replace_all.

Output: filePath, oldString, newString, originalFile, structuredPatch (hunks with oldStart, oldLines, newStart, newLines, lines), userModified, replaceAll, gitDiff (filename, status modified or added, additions, deletions, changes, patch, repository).

Read

Input: file_path, offset, limit.

Output varies by type:

typefile contents
textfilePath, content, numLines, startLine, totalLines, truncatedByTokenCap
imagebase64, type (jpeg, png, gif, webp), originalSize, dimensions
notebookfilePath, cells
pdffilePath, base64, originalSize
partsfilePath, originalSize, count, outputDir; plus top-level firstPage
file_unchangedfilePath; plus source: "seeded" when the earlier copy came from a CLAUDE.md or memory file

Write

Input: file_path, content.

Output: type (create or update), filePath, content, structuredPatch (empty for new files or skipped diffs), originalFile (None for new or very large files), gitDiff, userModified.

Glob

Input: pattern, path.

Output: durationMs, numFiles, filenames, truncated (100-file limit), totalMatches and countIsComplete (Claude Code 2.1.191+).

Grep

Input: pattern, path, glob, type, output_mode (content, files_with_matches, count), -i, -n, -B, -A, -C, context, -o, head_limit, offset, multiline.

Output: mode, numFiles (0 in content mode), filenames, content, numLines, numMatches, totalFiles (2.1.208+), totalLines (2.1.210+), appliedLimit, appliedOffset.

NotebookEdit

Input: notebook_path, cell_id, new_source, cell_type (code or markdown), edit_mode (replace, insert, delete).

Output: new_source, old_source, cell_id, cell_type, language, edit_mode, error, notebook_path, original_file, updated_file.

WebFetch and WebSearch

ToolInputOutput
WebFetchurl, promptbytes, code, codeText, result, durationMs, url
WebSearchquery, allowed_domains, blocked_domainsquery, results, durationSeconds

Task list tools

Available by default only on Claude 3.x, Opus 4 to 4.7, Sonnet 4 to 4.6 and Haiku 4.5 (Claude Code 2.1.268+). Elsewhere, opt in as described in todo tracking. CLAUDE_CODE_ENABLE_TASKS=0 swaps the four Task tools for TodoWrite.

ToolInputOutput
TodoWritetodos: list of content, status (pending, in_progress, completed), activeFormoldTodos, newTodos
TaskCreatesubject, description, activeForm, metadatatask: {id, subject}
TaskUpdatetaskId, status (adds deleted), subject, description, activeForm, addBlocks, addBlockedBy, owner, metadatasuccess, taskId, updatedFields, error, statusChange: {from, to}
TaskGettaskIdtask: id, subject, description, status, blocks, blockedBy, or None
TaskListnonetasks: id, subject, status, owner, blockedBy

Background task tools

  • TaskStop (aliases KillShell, KillBash). Input: task_id (or deprecated shell_id). Output: message, task_id, task_type, command.
  • TaskOutput was removed in Claude Code 2.1.277 (as was its BashOutput alias). Claude reads a background task's output file with Read. Deny rules naming either are silently ignored.

ExitPlanMode

Input: plan. Output: plan, isAgent, filePath, hasTaskTool, planWasEdited, awaitingLeaderApproval, requestId.

MCP resources

ToolInputOutput
ListMcpResourcesToolserver (optional)A list (not a dict) of uri, name, mimeType, description, server
ReadMcpResourceToolserver, uricontents (each uri, mimeType, text, blobSavedTo), error

A complete chat loop

A small terminal chat that keeps context across turns and supports new and interrupt:

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock

async def chat():
    client = ClaudeSDKClient(ClaudeAgentOptions(allowed_tools=["Read", "Grep"], permission_mode="default"))
    await client.connect()
    try:
        while True:
            line = await asyncio.to_thread(input, "\nyou> ")
            if line == "exit":
                break
            if line == "new":
                await client.disconnect()
                await client.connect()
                print("(fresh session)")
                continue
            if line == "interrupt":
                await client.interrupt()
                continue
            await client.query(line)
            async for m in client.receive_response():
                if isinstance(m, AssistantMessage):
                    print("".join(b.text for b in m.content if isinstance(b, TextBlock)), end="")
    finally:
        await client.disconnect()

asyncio.run(chat())

Sandbox

SandboxSettings

KeyDefaultMeaning
enabledFalseSandbox Bash commands
autoAllowBashIfSandboxedTrueAuto-approve Bash when sandboxed
excludedCommands[]Commands that always run outside the sandbox, such as ["docker *"], with no model involvement
allowUnsandboxedCommandsTrueLet the model set dangerouslyDisableSandbox on a call, which then goes through normal permissions
networkNoneSandboxNetworkConfig
ignoreViolationsNone{"file": [...], "network": [...]} patterns to ignore
enableWeakerNestedSandboxFalseWeaker nested sandbox for compatibility

The sandbox needs platform support, and on Linux bubblewrap and socat. If it cannot start, Python's default is to run unsandboxed with a warning on stderr (TypeScript defaults to failing). Add "failIfUnavailable": True to stop instead: it is not declared on the TypedDict but is forwarded and honoured, and the run ends in an error_during_execution result with the reason in errors.

opts = ClaudeAgentOptions(sandbox={
    "enabled": True,
    "failIfUnavailable": True,
    "network": {"allowedDomains": ["pypi.org", "files.pythonhosted.org"], "allowLocalBinding": True},
})

SandboxNetworkConfig

Applies to sandboxed Bash only, not WebFetch (which uses permission rules).

KeyMeaning
allowedDomainsDomains sandboxed processes may reach
deniedDomainsBlocked domains; beats allowedDomains
allowManagedDomainsOnlyManaged settings only; no effect from SDK options
allowUnixSocketsmacOS only: socket paths allowed
allowAllUnixSocketsAllow every Unix socket
allowLocalBindingAllow binding local ports, for dev servers
allowMachLookupmacOS only: XPC/Mach service names, trailing wildcard allowed
httpProxyPort, socksProxyPortProxy ports

Warning: Allowing /var/run/docker.sock effectively hands over the whole host through the Docker API. The built-in proxy also filters by requested hostname without inspecting TLS, so domain fronting can get round it; see secure deployment.

Handling unsandboxed requests

When allowUnsandboxedCommands is on and the model sets dangerouslyDisableSandbox: True, the call goes to can_use_tool, so you can decide. Note that can_use_tool needs streaming input, and a PreToolUse hook that simply returns {"continue_": True} keeps the stream open for it:

async def gate(tool, data, ctx):
    if tool == "Bash" and data.get("dangerouslyDisableSandbox"):
        if data.get("command", "").startswith("docker compose "):
            return PermissionResultAllow()
        return PermissionResultDeny(message="Unsandboxed execution not permitted")
    return PermissionResultAllow()

async def keep_open(data, tid, ctx):
    return {"continue_": True}

async def prompt():
    yield {"type": "user", "message": {"role": "user", "content": "Bring up the dev stack"}}

async for m in query(prompt=prompt(), options=ClaudeAgentOptions(
        sandbox={"enabled": True, "allowUnsandboxedCommands": True},
        permission_mode="default",
        can_use_tool=gate,
        hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[keep_open])]})):
    ...

Warning: With bypassPermissions and allowUnsandboxedCommands together, the model can leave the sandbox without asking anyone, apart from the actions no mode auto-approves. Do not combine them.