Skip to content

TypeScript SDK reference

The complete @anthropic-ai/claude-agent-sdk API: functions, Options, the Query object, every message type, hook types, tool schemas, permissions and sandbox settings.

This is the full reference for the @anthropic-ai/claude-agent-sdk package. It runs from installation through functions, the Options object and the Query handle, then message types, hooks, built-in tool schemas, permission types, supporting types and sandbox settings. Guides that explain the ideas behind each area are linked as you go.

Version notes matter a lot in this SDK. The SDK version tracks the bundled Claude Code version: SDK v0.3.191 ships Claude Code v2.1.191. So when a feature says "Claude Code v2.1.N", you need SDK v0.3.N or later unless you point at your own binary.

Installing

npm install @anthropic-ai/claude-agent-sdk

A native Claude Code binary for your platform comes in as an optional dependency, such as @anthropic-ai/claude-agent-sdk-darwin-arm64, so most installs need nothing else.

  • If your package manager skips optional dependencies, you get Native CLI binary for <platform>-<arch> not found. Install Claude Code separately and set pathToClaudeCodeExecutable.
  • Package managers that ignore npm's libc field (Yarn 1.x, for example) install both the glibc and musl Linux packages. From SDK v0.2.141 the right one is still chosen. In a container image you can delete the unused one, for example rm -rf node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl on a glibc x64 runtime.

Single-file executables with Bun

bun build --compile breaks binary resolution, because require.resolve cannot see into the compiled executable's $bunfs filesystem. From SDK v0.3.144, embed the binary as a file asset and extract it at start-up:

import cliAsset from "@anthropic-ai/claude-agent-sdk-linux-x64/claude" with { type: "file" };
import { extractFromBunfs } from "@anthropic-ai/claude-agent-sdk/extract";
import { query } from "@anthropic-ai/claude-agent-sdk";

const cli = extractFromBunfs(cliAsset);   // copies to a per-user temp dir; a no-op outside a compiled binary

for await (const m of query({ prompt: "List the services in docker-compose.yml", options: { pathToClaudeCodeExecutable: cli } })) {
  if (m.type === "result") console.log(m.subtype);
}

Each executable embeds one platform's binary, so match the import to your --target. To cross-compile, force-install the other platform package (npm install @anthropic-ai/claude-agent-sdk-linux-x64 --force). On Windows the subpath is claude.exe.

The /core entry point for bundlers

If you bundle the SDK with your own dependencies, import from @anthropic-ai/claude-agent-sdk/core (SDK v0.3.282+, TypeScript 5.0+ for its types).

  • It exports query(), startup(), tool(), createSdkMcpServer(), resolveSettings(), the rename, tag and delete session helpers, AbortError, runtime constants and every type.
  • It omits prewarm(), InMemorySessionStore, and the helpers that list, read, fork, import and summarise sessions. Use the root entry if you need those.
  • The root entry inlines its own zod and @modelcontextprotocol/sdk; /core imports them from your node_modules at the peer dependency ranges, avoiding duplicate copies.
  • Use one entry per process. Loading both gives you two copies of the SDK's classes and state.

Functions

query()

function query(params: {
  prompt: string | AsyncIterable<SDKUserMessage>;
  options?: Options;
}): Query;

The main entry point. Pass a string for a one-shot run, or an async iterable of SDKUserMessage for streaming input. Returns a Query, which is an async generator of SDKMessage with extra control methods.

startup()

Spawns the CLI and completes its initialise handshake before you have a prompt, so the first real query starts instantly.

function startup(params?: { options?: Options; initializeTimeoutMs?: number }): Promise<WarmQuery>;

initializeTimeoutMs defaults to 60000; the promise rejects if the handshake takes longer.

import { startup } from "@anthropic-ai/claude-agent-sdk";

const warm = await startup({ options: { cwd: "/srv/app", maxTurns: 10 } });   // at boot

// ...later, on the first request:
for await (const m of warm.query("Summarise today's error log")) { /* ... */ }

If you will not know the working directory until later, use prewarm().

prewarm()

Alpha, SDK v0.3.282+. Starts a spare process that is not yet tied to a session, which you later bind with claim(). Useful for apps that boot before the user picks a folder.

function prewarm(params?: { options?: Options; initializeTimeoutMs?: number }): Promise<SpareProcess>;
  • The spare waits in options.cwd if set, otherwise in a private temp directory under your Claude Code config directory. If spawnClaudeCodeProcess runs Claude Code elsewhere, set options.cwd to a path that exists there.
  • The working directory, SessionStart hooks, stdio MCP servers, CLAUDE.md and git context all wait for the claim.
  • A spare uses roughly 230 to 260 MB while waiting.
  • prewarm() throws if options sets resume, continue or forkSession.
  • Anything a claim cannot change (mcpServers, hooks, canUseTool, settingSources, systemPrompt, plugins and so on) is fixed for the spare's life, so keep one spare per distinct configuration.
import { prewarm } from "@anthropic-ai/claude-agent-sdk";

const spare = await prewarm({ options: { maxTurns: 20 } });

const q = spare.claim({ prompt: "What does this repo do?", options: { cwd: chosenFolder } });
spare.claimed.catch(err => {
  if (!String(err.message).startsWith("option_not_applied")) startFreshWithQuery();  // prompt never ran
});
try {
  for await (const m of q) { /* ... */ }
} catch (err) { /* a refused claim throws after yielding its error result */ }

tool()

Defines a type-safe tool for an in-process MCP server, using a Zod shape (Zod 3 or 4).

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>;
ExtraPurpose
annotationsMCP behaviour hints (below)
searchHintOne-line capability phrase shown in the deferred-tool list when tool search is active
alwaysLoadKeep this tool's full schema in the initial prompt instead of deferring it

ToolAnnotations (from @modelcontextprotocol/sdk/types.js; hints only, never a security control):

FieldDefaultMeaning
titlenoneDisplay title
readOnlyHintfalseDoes not change its environment
destructiveHinttrueMay make destructive changes (only meaningful when not read-only)
idempotentHintfalseRepeat calls have no extra effect (only meaningful when not read-only)
openWorldHinttrueTouches external systems
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const lookupPostcode = tool(
  "lookup_postcode",
  "Return the local authority and ward for a UK postcode",
  { postcode: z.string().regex(/^[A-Z0-9 ]{5,8}$/i) },
  async ({ postcode }) => {
    const info = await geo.lookup(postcode);
    return { content: [{ type: "text", text: `${info.authority}, ${info.ward}` }] };
  },
  { annotations: { readOnlyHint: true, openWorldHint: true } }
);

createSdkMcpServer()

function createSdkMcpServer(options: {
  name: string;
  version?: string;
  instructions?: string;
  tools?: Array<SdkMcpToolDefinition<any>>;
  alwaysLoad?: boolean;
  timeout?: number;
}): McpSdkServerConfigWithInstance;
OptionMeaning
nameServer name
versionOptional version string
instructionsReturned from initialize and shown to the model as an MCP instructions block
toolsTools from tool()
alwaysLoadKeep every tool from this server out of tool search deferral; combines with per-tool alwaysLoad
timeoutPer-call timeout in ms for this server, replacing MCP_TOOL_TIMEOUT. Whole number of at least 1000; other values ignored (SDK v0.3.248+)

See custom tools for a full walkthrough.

Session helpers

FunctionSignatureNotes
listSessions(options?: { dir?, limit?, includeWorktrees? = true }) => Promise<SDKSessionInfo[]>Newest first. Omit dir for all projects
getSessionMessages(sessionId, options?: { dir?, limit?, offset? }) => Promise<SessionMessage[]>User and assistant messages
getSessionInfo(sessionId, options?: { dir? }) => Promise<SDKSessionInfo | undefined>One session's metadata without a scan
renameSession(sessionId, title, options?: { dir? }) => Promise<void>Appends a title; latest wins. Title must be non-empty after trimming
tagSession(sessionId, tag | null, options?: { dir? }) => Promise<void>null clears

SDKSessionInfo

FieldMeaning
sessionIdUUID
summaryCustom title, latest prompt, generated summary or first prompt
lastModifiedms since epoch
fileSizeBytes; local JSONL storage only
customTitleTitle set via --name, /rename, a hook's sessionTitle or renameSession(), else the generated title
firstPromptFirst meaningful prompt
gitBranchBranch at session end
cwdWorking directory
tagFrom tagSession()
createdAtFrom the first entry's timestamp

SessionMessage: type (user or assistant), uuid, session_id, message (raw payload), parent_tool_use_id (the Agent or Skill call that started a subagent, else null), parent_agent_id (the spawning subagent's ID for nested subagents; Claude Code v2.1.202+).

import { listSessions, getSessionMessages } from "@anthropic-ai/claude-agent-sdk";

const [last] = await listSessions({ dir: "/srv/app", limit: 1 });
if (last) {
  for (const m of await getSessionMessages(last.sessionId, { dir: "/srv/app", limit: 50 })) {
    console.log(m.type, m.uuid);
  }
}

resolveSettings()

Alpha. Computes the effective settings for a directory with the CLI's own merge engine, without spawning anything.

function resolveSettings(options?: {
  cwd?: string;                     // default process.cwd()
  settingSources?: SettingSource[]; // default: all
  managedSettings?: Settings;
  serverManagedSettings?: Settings;
}): Promise<ResolvedSettings>;

It returns effective (the merged settings), provenance (which source supplied each top-level key) and sources (each source's raw settings, lowest precedence first, with optional path and policyOrigin).

It differs from a live session in three ways: it reads MDM sources but does not run a policyHelper; it only includes server-managed settings you pass in; and it reports permissions.defaultMode verbatim from every tier, including auto and bypassPermissions from project or local files that a live session would ignore.

const { effective, provenance } = await resolveSettings({ cwd: "/srv/app" });
console.log(effective.cleanupPeriodDays, "from", provenance.cleanupPeriodDays?.source);

Options

Grouped by purpose. All are optional.

Tools and permissions

OptionTypeDefaultMeaning
toolsstring[] | { type: 'preset'; preset: 'claude_code' }noneBuilt-in tool set
allowedToolsstring[][]Auto-approve these. Does not restrict others. Naming a task-tracking tool opts the session in
disallowedToolsstring[][]Bare name removes the tool; scoped rule denies matching calls in every mode
permissionModePermissionModenoneStarting mode; omitted may mean auto mode
allowDangerouslySkipPermissionsbooleanfalseRequired to use bypassPermissions, at start or later via setPermissionMode()
canUseToolCanUseToolnoneCalled only when the flow reaches a prompt
permissionPromptToolNamestringnoneMCP tool that answers permission prompts
permissionPrompts'host' | 'none''host''none' denies calls that would prompt (Claude Code v2.1.259+)
planModeInstructionsstringnoneReplaces the plan-mode workflow body; the read-only preamble and ExitPlanMode footer are kept
toolAliasesRecord<string, string>noneMap built-ins to MCP tools, e.g. { Bash: 'mcp__workspace__bash' }
toolConfigToolConfignoneBuilt-in tool behaviour
sandboxSandboxSettingsnoneSee Sandbox

Prompt, model and reasoning

OptionTypeDefaultMeaning
systemPromptsee belowminimal prompt
modelstringCLI defaultAlias or full ID
fallbackModelstringnoneComma-separated list allowed
thinkingThinkingConfig{ type: 'adaptive' } on supported models
maxThinkingTokensnumbernoneDeprecated
effort'low' | 'medium' | 'high' | 'xhigh' | 'max'noneWorks with adaptive thinking
betasSdkBeta[][]
outputFormat{ type: 'json_schema'; schema }noneStructured outputs
taskBudget{ total: number }noneAlpha. Tells the model its remaining token budget so it can pace itself
agentstringnoneAgent to run as the main thread; must be defined in agents or settings
agentsRecord<string, AgentDefinition>noneProgrammatic subagents

systemPrompt accepts:

  • a string (custom prompt);
  • a string[] with SYSTEM_PROMPT_DYNAMIC_BOUNDARY between static and per-request parts;
  • { type: 'custom'; prompt: string | string[]; snapshot?: boolean };
  • { type: 'preset'; preset: 'claude_code'; append?: string; excludeDynamicSections?: boolean; snapshot?: boolean }.

The custom object form and snapshot need SDK v0.3.257. Details in modifying system prompts. Note that outputStyle is not an option: set it inside settings.

Limits

OptionTypeMeaning
maxTurnsnumberCap on tool-use round trips
maxBudgetUsdnumberStop when this call's estimated spend reaches the value; restored totals excluded

Sessions

OptionTypeDefaultMeaning
continuebooleanfalseContinue the latest conversation
resumestringnoneSession ID to resume
forkSessionbooleanfalseBranch to a new ID when resuming
resumeSessionAtstringnoneResume at a specific message UUID
resumeDropsTurnstringnoneWith resumeSessionAt: the prompt UUID whose turn is being discarded. Resume is refused if the discarded range holds anything else (Claude Code v2.1.223+)
sessionIdstringgeneratedUse this UUID
titlestringnoneDisplay title. A resumed session's stored title wins
persistSessionbooleantruefalse writes nothing to disk; cannot be resumed
sessionStoreSessionStorenoneMirror transcripts to external storage
sessionStoreFlush'batched' | 'eager''batched'Alpha
loadTimeoutMsnumber60000Alpha. Timeout per load() / listSubkeys() while resuming from a store
enableFileCheckpointingbooleanfalseFile checkpointing

Process and environment

OptionTypeDefaultMeaning
cwdstringprocess.cwd()Working directory
additionalDirectoriesstring[][]Extra directories, passed as --add-dir (their skills, commands and agents load with the project source)
projectConfigRootstringnoneAbsolute path of the trusted checkout that cwd is a worktree of. Project settings, .mcp.json and .claude/ content load from here, CLAUDE_PROJECT_DIR points here, and hooks, helpers and stdio MCP servers start here. CLAUDE.md and .claude/rules/ still load from cwd (Claude Code v2.1.275+)
envRecord<string, string | undefined>process.envReplaces the environment. Spread process.env. CLAUDE_AGENT_SDK_CLIENT_APP names your app in the User-Agent
executable'bun' | 'deno' | 'node'detectedJS runtime
executableArgsstring[][]
extraArgsRecord<string, string | null>{}Extra CLI flags
pathToClaudeCodeExecutablestringbundled binaryOnly when the bundled one is missing or unsupported
spawnClaudeCodeProcess(o: SpawnOptions) => SpawnedProcessnoneRun Claude Code in a VM, container or remote host
abortControllerAbortControllernew oneCancel the run
stderr(data: string) => voidnoneCLI stderr
debugbooleanfalseDebug mode
debugFilestringnoneDebug log path (implies debug)

Configuration sources and extensions

OptionTypeDefaultMeaning
settingSourcesSettingSource[]all[] skips user, project and local
settingsstring | SettingsnoneInline object, file path or JSON string; fills the flag-settings layer. Change later with applyFlagSettings()
managedSettingsSettingsnonePolicy-tier settings from your host. Ignored on machines with admin-deployed managed settings unless the top managed source sets parentSettingsBehavior: 'merge', and never merged while a policyHelper supplies settings. Merged values pass a restrictive-only filter. With CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST, three keys are read directly from it: model configuration (v2.1.222+), modelPricing when no managed source sets it (v2.1.246+), and the ENABLE_TOOL_SEARCH env entry (v2.1.247+)
mcpServersRecord<string, McpServerConfig>{}
strictMcpConfigbooleanfalseIgnore .mcp.json, user settings, plugin servers and claude.ai connectors
pluginsSdkPluginConfig[][]Plugins
skillsstring[] | 'all'noneSkills Claude may invoke. Adds Skill to allowedTools; include 'Skill' if you pass tools. Bad names throw (SDK v0.3.221+)
hooksPartial<Record<HookEvent, HookCallbackMatcher[]>>{}
onElicitation(req, { signal }) => Promise<ElicitationResult>noneHandles MCP elicitation when no hook does; unhandled requests are declined

Stream shape and extras

OptionTypeDefaultMeaning
includePartialMessagesbooleanfalseYield stream_event messages
includeHookEventsbooleanfalseYield hook started, progress and response messages. SessionStart and Setup always do. Notification, SessionEnd, PreCompact and PostCompact never produce a started message; they still produce progress while a command hook running over a second prints output, and a response only for background hooks
forwardSubagentTextbooleanfalseForward foreground subagents' text and thinking as messages with parent_tool_use_id set
agentProgressSummariesbooleanfalseOne-line subagent summaries on task_progress events
promptSuggestionsbooleanfalseEmit prompt_suggestion messages after turns
verbatimPromptsbooleanfalseSet client_composed: true on every user message (SDK v0.3.280+, Claude Code v2.1.248+)

Slow or stalled APIs

Pass these in env (remember to spread process.env):

VariableDefaultEffect
API_TIMEOUT_MS600000Per-request timeout for main loop and subagents
CLAUDE_CODE_MAX_RETRIES10, max 15Each retry gets a full timeout window. CLAUDE_CODE_RETRY_WATCHDOG=1 retries capacity errors indefinitely and from v2.1.199 raises other transient retries to 300 and removes the cap
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSstream idle timeout + 5 min (or 600000 with the watchdog off)Aborts a silent subagent; background ones are marked failed with partial results
CLAUDE_ENABLE_STREAM_WATCHDOGon0 disables the body-stall watchdog
CLAUDE_STREAM_IDLE_TIMEOUT_MS300000 minimumWatchdog idle limit

With includePartialMessages, a gateway that keeps a response alive with pings yields ping stream events; treat them as liveness.

The Query object

query() returns this. It is an AsyncGenerator<SDKMessage, void> with control methods. Several only work in streaming input mode.

MethodReturnsPurpose
interrupt()SDKControlInterruptResponse | undefinedStop the current turn (streaming only). Returns a receipt when the CLI advertises interrupt_receipt_v1 (v2.1.205+)
rewindFiles(userMessageId, { dryRun? })RewindFilesResultRestore files; needs enableFileCheckpointing
setPermissionMode(mode)voidStreaming only
setModel(model?)voidStreaming only; undefined or "default" resets
setMaxThinkingTokens(n | null)voidDeprecated; null resets to the session default
applyFlagSettings(settings)voidChange settings mid-session (streaming only)
updateSettings(source, settings)voidPersist one allowlisted key to disk (SDK v0.3.257+)
initializationResult()SDKControlInitializeResponseCached init data
reinitialize()SDKControlInitializeResponseRe-send initialize after a transport gap so pending permission requests reach canUseTool again. Make the callback idempotent per request ID (v2.1.195+)
supportedCommands()SlashCommand[]Tracks mid-session changes from SDK v0.3.216
supportedModels()ModelInfo[]
supportedAgents()AgentInfo[]Captured at init
mcpServerStatus()McpServerStatus[]
getContextUsage({ detail? })SDKControlGetContextUsageResponse/context data; detail needs SDK v0.3.257
readFile(path, { maxBytes?, encoding? })SDKControlReadFileResponse | nullDefault cap 1 MB, ceiling 10 MB; 'base64' for binaries (SDK v0.2.121+)
reloadPlugins({ holdOnCacheImpact? })SDKControlReloadPluginsResponseSDK v0.2.85+; option v0.3.268+
reloadSkills()SDKControlReloadSkillsResponseSDK v0.3.163+
reloadOutputStyles()SDKControlReloadOutputStylesResponseSDK v0.3.261+
accountInfo()AccountInfo
reconnectMcpServer(name)voidPrefers servers you configured via mcpServers or setMcpServers() over settings-file entries of the same name (v2.1.257+)
toggleMcpServer(name, enabled)voidDisable removes the server's tools
setMcpServers(servers)McpSetServersResultReplace SDK-managed servers
readMcpResource(server, uri)SDKControlMcpReadResourceResponseAlpha. MCP Apps ui:// resources (SDK v0.3.280+)
streamInput(stream)voidAdd turns
stopTask(taskId)voidStop a background task
close()voidKill the process and clean up

applyFlagSettings()

Changes settings on a running session (TypeScript only; streaming input only). It writes to the flag-settings layer over whatever settings set at start-up.

When it appliesKeys
Next turneffortLevel, ultracode, permissions, hooks, skillOverrides, fastMode, agent (switching agent also applies its model and hooks; its system prompt applies next turn, or after compaction in sessions that reuse a recorded prompt)
Current turnmodel: the in-flight response finishes on the old model, then the rest of the turn uses the new one. Subagents keep their own model. (Before v2.1.212, waited for the next turn.)
Never mid-sessionSystem prompt options; resolved once at start

Rules:

  • Successive calls shallow-merge top-level keys, so a second permissions object replaces the first entirely.
  • Pass null to clear a key; it falls back to the settings option and then lower sources. undefined does nothing (JSON drops it).
  • Clearing model resets to Claude Code's default even if a settings file sets one.
  • effortLevel: null returns to the model's default effort; agent: null runs with no agent from next turn (and drops any model the agent applied); ultracode: null turns it off without changing effort.
  • effortLevel also accepts "ultracode" at runtime (v2.1.203+), but the type does not declare it, so in TypeScript pass { ultracode: true, effortLevel: "xhigh" } instead. Before v2.1.284, ultracode alone also set xhigh.
const q = query({ prompt: userTurns() });
await q.applyFlagSettings({ permissions: { deny: ["WebFetch", "Bash(curl *)"] } });  // after reading untrusted input
await q.applyFlagSettings({ model: "claude-opus-4-6" });
await q.applyFlagSettings({ model: null });   // back to the default

updateSettings()

Persists exactly one allowlisted string key:

  • "localSettings" accepts outputStyle, merged into .claude/settings.local.json; applies on the next request.
  • "userSettings" accepts effortLevel, saved as the default for the current model under modelSettings in your user settings. max writes nothing (session-only). The running session's effort is unchanged; use applyFlagSettings() for that. Needs SDK v0.3.277.

It rejects other keys, remote transports, and sources excluded by settingSources. Deleting keys is not supported.

toggleMcpServer() version notes

Disabling a stdio, SSE or HTTP server added with setMcpServers() removes its tools from Claude Code v2.1.285. Disabling an in-process server (from createSdkMcpServer()) disconnects it from v2.1.286, and its running calls fail immediately with error results.

WarmQuery

Returned by startup(). Implements AsyncDisposable, so await using works.

MethodPurpose
query(prompt)Send the first prompt to the ready process. Once only
close()Discard without sending

SpareProcess

Alpha. Returned by prewarm(). Implements AsyncDisposable.

MemberPurpose
claim({ prompt, options })Bind to a session in options.cwd (required) and send the first message. Returns a Query synchronously. Once only
claimedResolves with { cwd, sessionId, parkedMs?, sdkMcpSettled }. Rejects if refused, if the process died or was closed, or with a message starting option_not_applied when model or maxThinkingTokens was not honoured
exitedSettles when the process exits. Replace a spare that exits unclaimed
close()Kill it; before a claim this rejects claimed

A claim can set additionalDirectories, model, permissionMode, maxThinkingTokens, a flag-settings overlay in settings, appendSystemPrompt, title, agents and per-session tokens in env. Claims are refused for a folder that does not exist or whose project settings set env, agent or model; the prompt then gets an error result starting not_claimed and the query throws. On any rejection other than option_not_applied, the prompt did not run.

Control responses

SDKControlInitializeResponse

From initializationResult() and reinitialize():

FieldMeaning
commandsSlashCommand[]
agentsAgentInfo[]
output_style, available_output_stylesCurrent and available styles
modelsModelInfo[]
accountAccountInfo
fast_mode_stateoff, cooldown or on; always reported from v2.1.219
fast_mode_disabled_reasonWhy fast mode is blocked (codes under result messages)
hooks_appliedWhether the request's hooks were registered (SDK v0.3.238+). true on first init and on repeats over stdin (new hooks replace old); false on repeats to a remote session; absent if no hooks were sent
sdk_mcp_manifests_parkedInternal to in-process SDK MCP servers; you do not set or read it

The response wrapper (not the payload) also carries pending_permission_requests: complete control_request messages this process issued and has not resolved. The SDK re-dispatches them to canUseTool, so handle repeated request IDs idempotently. Always present from v2.1.268.

SDKControlInterruptResponse

type SDKControlInterruptResponse = { still_queued: string[]; cancelled?: string[] };

still_queued lists UUIDs of user messages pending when the interrupt arrived (including ones already taken for the next turn). After the first turn has started, they will still be processed, possibly merged into one turn, so do not resend them. If you interrupt before the first turn starts, that turn aborts and its messages get no reply.

Caveats: only messages with a UUID appear; only main-thread messages; and you may see UUIDs you never sent (scheduled task triggers, for example), which you should ignore.

Clients driving the control protocol directly can send cancel_queued: true (advertised as interrupt_cancel_queued_v1, v2.1.219+) to cancel those messages; they then appear under cancelled. interrupt() itself never sends it. The receipt is a snapshot taken when the interrupt is processed and arrives before the interrupted turn's result, so read it rather than inspecting the queue afterwards.

SDKControlGetContextUsageResponse

From getContextUsage(). The default detail: 'full' counts each category with token-counting requests (not in the stream, not billed on the Anthropic API). detail: 'summary' uses the last response and local estimates instead: no extra requests, approximate numbers.

FieldMeaning
categories{ name, tokens, color, isDeferred?, kind }, where kind is used, free, buffer or deferred (SDK v0.3.268+). Classify by kind, not name
totalTokens, maxTokens, rawMaxTokens, percentageUsage against the model window or the lower auto-compact window; rawMaxTokens equals maxTokens
gridRowsDisplay grid cells (color, isFilled, categoryName, tokens, percentage, squareFullness)
modelModel
memoryFiles{ path, type, tokens }
mcpTools{ name, serverName, tokens, isLoaded? }
agents{ agentType, source, tokens }
slashCommands?{ totalCommands, includedCommands, tokens }
skills?{ totalSkills, includedSkills, tokens, skillFrontmatter: { name, source, tokens }[] }; per-skill counts measure the listing entry actually sent
autoCompactThreshold?, isAutoCompactEnabledAuto-compact
messageBreakdown?Tool call, tool result, attachment, assistant, user, redirected and unattributed tokens, plus toolCallsByType and attachmentsByType
apiUsageLatest response's usage, or null

deferredBuiltinTools, systemTools and systemPromptSections are declared but left unset. Sending /context as a prompt instead attaches an SDKContextUsage to the reply (SDK v0.3.232+).

Other control responses

TypeShape and notes
SDKControlReadFileResponse{ contents, absPath, truncated?, encoding?: 'base64' }. readFile() only serves regular files inside the session's working directories plus a few of Claude Code's own session files; Read deny and ask rules still apply, and a broad Read allow does not widen it. Anything else returns null
SDKControlReloadPluginsResponsecommands, agents, plugins (name, path, optional source, version from the manifest, so validate it), mcpServers, error_count, plus held and cache_impact when you pass holdOnCacheImpact. held: true means not applied (call again without the option to force); false means applied with no cache impact. cache_impact lists mcp_servers_added, mcp_servers_removed (as plugin:<plugin>:<server>) and lsp_tool_change (adds, may-add, removes, may-remove or null). Executables older than v2.1.268 ignore the option. Read agents here after a reload because supportedAgents() keeps the init list
SDKControlReloadSkillsResponse{ skills: SlashCommand[] }
SDKControlReloadOutputStylesResponse{ available_output_styles: string[] }
SDKControlMcpReadResourceResponse{ contents: { uri, mimeType?, text?, blob?, _meta? }[] }. Only ui:// URIs on a connected, non-SDK server; available when init capabilities include mcp_read_resource_v1. _meta keys under com.anthropic/ are stripped. Treat the HTML as untrusted and render it sandboxed

Configuration types

AgentDefinition

type AgentDefinition = {
  description: string;
  prompt: string;
  tools?: string[];
  disallowedTools?: 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;
};
FieldMeaning
description, promptRequired: when to use it, and its system prompt
toolsAllowlist; omit to inherit. Use skills, not 'Skill' here, to preload skills
disallowedToolsAlso accepts mcp__server, mcp__server__*, mcp__*
modelfable, opus, sonnet, haiku, inherit or a full ID
mcpServersNames from the parent's config or inline records
skillsPreloaded skills
initialPromptFirst turn when used as the main-thread agent
maxTurns, background, effort, memoryAs named
omitClaudeMdSkip user, project and local CLAUDE.md as a subagent (SDK v0.3.271+)
permissionModeSubject to inheritance rules
criticalSystemReminder_EXPERIMENTALExperimental reminder added to the system prompt

AgentMcpServerSpec = string | Record<string, McpServerConfigForProcessTransport>, where the latter is any of stdio, SSE, HTTP or SDK config. See subagents.

SettingSource and precedence

ValueFile
'user'~/.claude/settings.json
'project'.claude/settings.json
'local'.claude/settings.local.json

Omitting settingSources loads all three, like the CLI. Include 'project' to load CLAUDE.md. Precedence, highest first: local, project, user; programmatic options (agents, allowedTools, settings) beat all three; managed policy beats programmatic options. Claude Code features in the SDK lists inputs that load regardless.

PermissionMode

"default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" | "auto". Behaviour is described in permissions.

CanUseTool

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>;
OptionMeaning
signalAborts if the request is cancelled
suggestionsPermission updates that would stop future prompts. Bash suggestions target localSettings, so returning one in updatedPermissions persists it to .claude/settings.local.json
blockedPathPath that triggered the prompt
mcpServerFor mcp__* tools, the server and where it was defined (SDK v0.3.274+)
decisionReasonWhy the prompt happened
defaultToNoFocus your UI on decline, no pre-selected approve, no one-key approve (SDK v0.3.268+)
suppressAlwaysAllowRuleDo not offer "always allow" (SDK v0.3.268+)
toolUseIDThe call
agentIDSubagent ID if inside one
requestIdThe control_request ID; echo it if you answer out of band

Return a PermissionResult normally. Return null only if your app already sent the control_response for this requestId through its own channel; otherwise the call blocks forever, since prompts never time out. requestId and null need Claude Code v2.1.199.

type PermissionResult =
  | { behavior: "allow"; updatedInput?: Record<string, unknown>; updatedPermissions?: PermissionUpdate[]; toolUseID?: string }
  | { behavior: "deny"; message: string; interrupt?: boolean; toolUseID?: string };

ToolConfig

{ askUserQuestion?: { previewFormat?: "markdown" | "html" } }. Setting previewFormat opts in to the preview field on AskUserQuestion options; without it Claude sends no previews. See user input.

MCP server configs

TypeFields
McpStdioServerConfigtype?: "stdio", command, args?, env?
McpSSEServerConfigtype: "sse", url, headers?
McpHttpServerConfigtype: "http", url, headers?
McpSdkServerConfigWithInstancetype: "sdk", name, timeout?, instance
McpClaudeAIProxyServerConfigtype: "claudeai-proxy", url, id (status reports only)

McpServerConfig is the union of the first four.

SdkPluginConfig

{ type: "local"; path: string; skipMcpDiscovery?: boolean }. skipMcpDiscovery loads the plugin's skills, hooks, agents and commands but ignores its .mcp.json and manifest mcpServers, for when your app manages those connections.

Messages

The SDKMessage union

Everything the query yields is one of these. Switch on type, and for type: "system" on subtype.

type / subtypeTypeScript typeWhat it is
assistantSDKAssistantMessageClaude's output
userSDKUserMessage, SDKUserMessageReplayYour input, tool results, replays
resultSDKResultMessageEnd of a turn or call
stream_eventSDKPartialAssistantMessageRaw stream events (with includePartialMessages)
system / initSDKSystemMessageSession start-up details
system / compact_boundarySDKCompactBoundaryMessageCompaction happened
system / statusSDKStatusMessageStatus changes such as compacting
system / informationalSDKInformationalMessageNotices, warnings, hook feedback
system / hook_started, hook_progress, hook_responseHook lifecycle messages
system / plugin_installSDKPluginInstallMessageMarketplace install progress
system / permission_deniedSDKPermissionDeniedMessageA denial decided without a prompt
system / task_started, task_progress, task_updated, task_notificationBackground task messages
system / background_tasks_changedSDKBackgroundTasksChangedMessageFull live task set
system / thinking_tokensSDKThinkingTokensMessageThinking progress estimates
system / session_state_changedSDKSessionStateChangedMessageRunning, idle or needs action
system / files_persistedSDKFilesPersistedEventCheckpoints saved
system / commands_changedSDKCommandsChangedMessageCommand list changed
system / worker_shutting_downSDKWorkerShuttingDownMessageGraceful worker exit
system / local_command_outputSDKLocalCommandOutputMessageDeclared but not emitted
tool_progressSDKToolProgressMessageLong-running tool heartbeat
tool_use_summarySDKToolUseSummaryMessageSummary of tool use
auth_statusSDKAuthStatusMessageAuthentication flow
rate_limit_eventSDKRateLimitEventRate limit state
prompt_suggestionSDKPromptSuggestionMessagePredicted next prompt
conversation_resetSDKConversationResetMessage/clear and friends

The union also includes SDKNotificationMessage, SDKMemoryRecallMessage, SDKElicitationCompleteMessage, SDKAPIRetryMessage and SDKMirrorErrorMessage (the mirror_error system message emitted when a session store batch cannot be delivered).

SDKAssistantMessage

FieldMeaning
uuid, session_idIdentifiers
messageA BetaMessage from the Anthropic SDK: id, content, model, stop_reason, usage
parent_tool_use_idSet inside subagents
errorSDKAssistantMessageError when the response failed
abortedtrue when an interrupt cut the message short: no stop_reason, content may stop mid-word (SDK v0.3.214+)
timestampISO 8601 time the content finished, from the producing machine's clock. Display only; do not sort by it
context_usageSDKContextUsage on the reply to a /context prompt (SDK v0.3.232+)
user_message_uuid, user_message_uuids, resume_reasonReply matching, described below

SDKAssistantMessageError values: authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, rate_limit (429 against your quota), overloaded (529, server at capacity), invalid_request, model_not_found (model missing or not available to you), server_error, max_output_tokens, cloud_credential_error (no usable AWS or Google Cloud credentials on the machine; usually an expired sign-in; SDK v0.3.267+) and unknown.

Per-message usage.output_tokens is a placeholder from message_start; read real output counts from the result. One API response can produce several assistant messages sharing a message.id.

SDKUserMessage

type SDKUserMessage = {
  type: "user";
  uuid?: UUID;
  session_id?: string;
  message: MessageParam;
  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[];
};

Fields you set when sending:

FieldEffect
uuidSet one if you want replies matched back to this message
originSet { kind: "human" } for text the user typed. Without it, the message is unattributed and human-only checks (such as the ultracode workflow keyword) reject it. (Before v2.1.210, absent meant human.)
shouldQuery: falseAppend to the transcript without starting a turn; merged into the next message that does
client_composed: trueDeliver text as written: no @path or @server:resource expansion, no /command dispatch (SDK v0.3.280+, Claude Code v2.1.248+)
pasted_contentContent pasted into your UI, one entry per paste (string or blocks); appended after typed text, possibly wrapped in <pasted_content> tags. Non-text blocks ignored (SDK v0.3.277+)
inline_pastesSubstrings of message.content that were pasted, so Claude Code can tag them where they sit; only in the last text block (SDK v0.3.280+)
priorityWhen a message sent mid-turn is read (below)
isSyntheticMarks a message as not typed by the user

priority:

  • 'next' or absent: read in the same turn once current tool calls finish; starts the next turn if the turn ends first.
  • 'later': held until the turn ends, then sent as a new turn.
  • 'now' with origin: { kind: "human" }: from v2.1.286, movable work (shell commands, subagents, MCP calls; WebFetch and WebSearch from v2.1.287) moves to the background and Claude reads the message in the same turn. Otherwise the turn is interrupted.
  • 'now' without that origin: interrupt, then read.
const redirect: SDKUserMessage = {
  type: "user",
  message: { role: "user", content: "Stop the migration and just report what is done" },
  parent_tool_use_id: null,
  priority: "now",
  origin: { kind: "human" }
};

Reading tool_use_result

On a user message carrying a tool_result, tool_use_result is the tool's structured output (see tool output types), typed unknown. Special cases:

  • Agent: an AgentOutput. Render from it, not from the text. A completed result's content holds the report, or a short note if the subagent reported through a SubagentHandback call. In auto mode from v2.1.271 every non-fork subagent reports that way, and Claude receives the report as a separate message.
  • Detached WebFetch or WebSearch: when moved to the background to deliver a 'now' message, tool_use_result is { detachedToolCall: true } and no second result follows for that tool_use_id. Mark the row as backgrounded (v2.1.287+).
  • MCP resource_link blocks: { resourceLinks: SDKMcpResourceLink[] }. Omitted when none and on subagent results; at most 50 links or 64 KiB (SDK v0.3.257+).
  • MCP structuredContent: tool_use_result.structuredContent holds it and .content holds the McpOutput. Not on subagent results.
  • Oversized structuredContent: above 1,048,576 characters of JSON it is dropped and structuredContentOmitted: true is set instead; in-process SDK servers and tools with an MCP Apps _meta.ui resource are exempt (v2.1.287+).

SDKUserMessageReplay

Like SDKUserMessage but with a required uuid and session_id, and isReplay: true. Turns injected from outside (origin peer or channel) always appear as replays, whether they arrived mid-turn or started a turn while idle (from v2.1.207).

SDKResultMessage

Two arms: subtype: "success" and the error subtypes error_max_turns, error_during_execution, error_max_budget_usd, error_max_structured_output_retries.

Fields on both arms:

FieldMeaning
uuid, session_idIdentifiers
duration_ms, duration_api_msWall and API time
is_errorTrue on error arms; on success, true if the last request failed
num_turnsTurns
stop_reasonAPI stop reason, or tool_deferred
total_cost_usdEstimate covering the same calls as modelUsage
usageNonNullableUsage, main loop only, per turn in streaming mode
modelUsagePer-model totals: main loop, subagents, compaction, Workflow agents. Excludes helpers like the permission classifier and token counting. Includes restored totals on resume; cumulative in streaming mode
permission_denialsSDKPermissionDenial[] (tool_name, tool_use_id, tool_input); the authoritative denial record
queued_turn_countHuman-origin messages still queued (SDK v0.3.242+). 0 does not exclude a further turn; absent after a crash
terminal_reasonWhy the loop ended (below)
result_indexPosition in delivery order from 0; a gap means a lost result (SDK v0.3.268+)
fast_mode_state, fast_mode_disabled_reasonFast mode status
originOrigin of the triggering message
user_message_uuid, user_message_uuids, resume_reasonReply matching

Success only: result (final text), api_error_status, ttft_ms (to first complete assistant message), ttft_stream_ms (to message_start), local_command (the command a turn ran without entering the loop, lowercased with underscores, e.g. reload_plugins; mcp for MCP commands and /mcp; custom for your own; SDK v0.3.268+), request_sent_wall_ms (epoch ms the API request went out, alongside user_message_uuid), first_content_frame_ms (to the first content block event, thinking included; SDK v0.3.260+), structured_output, deferred_tool_use ({ id, name, input } when a hook returned defer), and the first_stream_post_* / first_text_post_* upload timings that only claude.ai-streamed sessions record.

Error only: errors (strings) and startup_failure_reason.

terminal_reason values: 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, turn_setup_failed.

fast_mode_disabled_reason codes (v2.1.219+; also on the init message and initialise response):

CodeMeaning
freeNo paid subscription or usage credits
preferenceOrganisation disabled fast mode
extra_usage_disabledUsage credits are off
network_errorAvailability check could not reach api.anthropic.com
unknownCould not determine
not_first_partyNot on the Anthropic API
disabled_by_envCLAUDE_CODE_DISABLE_FAST_MODE is set
model_not_allowedFast-mode Opus model not in availableModels
sdk_opt_in_requiredPass fastMode: true in settings or via applyFlagSettings()
pendingCheck still running

During a post-rate-limit cooldown you get fast_mode_state: "cooldown" with no reason.

Origins on results. Injected follow-ups (finished background tasks, routine triggers, verified messages from your other sessions) carry origin.kind === "task-notification". Declared scheduled runs do too, so do not suppress on kind alone. When several task completions are answered in one turn, each still gets a result, with all but the last empty and num_turns: 0. Results before any user turn (start-up errors) have no origin.

startup_failure_reason

On the error_during_execution result written before exit on a known start-up failure (SDK v0.3.274+), with zeroed totals and errors matching stderr. By default only worktree resume failures and a refused continue of a background-held session produce this result; set CLAUDE_CODE_STARTUP_FAILURE_RESULTS=1 to get it for every reason.

ValueCause
org_pin_api_key_conflictManaged settings require a first-party or Cloud gateway sign-in but an API key, auth token or apiKeyHelper is configured
provider_not_allowedProvider or endpoint not in the managed allowlist (v2.1.285+)
org_verify_failedCould not verify the organisation against the pin
org_pin_mismatchSigned in to a disallowed organisation
managed_settings_invalidManaged settings unreadable, pin names no org, or model restrictions leave no Default model
remote_settings_required_unavailableRequired managed settings could not load
gateway_signin_requiredCloud gateway ended the sign-in
gateway_access_deniedCloud gateway returned 403 for managed settings
proxy_invalidProxy setting is not a full URL
temp_dir_unusablePer-user temp directory unsafe or not creatable
cwd_unavailableWorking directory gone or unreadable
shell_tool_missingWindows with no Git Bash and no usable PowerShell
session_held_by_backgroundConversation is running as a background session
worktree_resume_refusedWorktree failed safety checks, or resume launched from inside it
worktree_unverifiedWorktree could not be verified now; retry may work
cli_version_too_oldBelow Anthropic's minimum version
bypass_rootBypass mode requested as root

Matching replies to your messages

Set a uuid on each SDKUserMessage and Claude Code echoes it back.

  • user_message_uuid names the message a turn is answering. If several messages were merged into one turn, it names the last; user_message_uuids (SDK v0.3.259+, up to 64 entries, always including user_message_uuid) lists all of them, plus any regular message picked up mid-turn.
  • For a synthetic message (isSynthetic: true), the turn answers it until a regular message is picked up between tool calls (echo needs SDK v0.3.265).
  • For a turn re-run under CLAUDE_CODE_RESUME_INTERRUPTED_TURN, the re-run answers the interrupted turn's last regular message (SDK v0.3.268) and carries resume_reason (a token such as interrupted_turn) on its result and on reply frames that carry user_message_uuid.
  • Other self-generated turns answer nothing of yours until they pick up a message.

Where the echo appears: every result of a turn that answered one of your messages (complete from SDK v0.3.265); the turn's first assistant message and, with partial messages on, the first non-ping stream event (SDK v0.3.246+), again whenever the answered message changes; and every thinking_tokens frame (SDK v0.3.260+). It never appears on other reply frames, subagent frames, turns that answer nothing of yours, messages you sent without a uuid, or crash results.

SDKSystemMessage (init)

FieldMeaning
agentsAgent names
apiKeySourceApiKeySource
betasActive betas
claude_code_versionVersion
cwd, model, permissionMode, output_styleSession state
toolsTool names (the Agent tool is still listed as Task)
mcp_servers{ name, status, source? } (source SDK v0.3.274+)
slash_commandsUsable commands
terminal_slash_commandsThose bound to a local terminal, such as exit, for remote clients to hide (SDK v0.3.229+)
skillsUser-invocable skills
plugins{ name, path }
plugin_errors{ plugin, type, message, path? } (declared SDK v0.3.283+). plugin is a tag such as inline[0] when the directory itself failed; path is then its absolute path. type is an open set such as path-not-found or manifest-validation-error
fast_mode_state, fast_mode_disabled_reasonFast mode
effortOnly sent to Remote Control clients
capabilitiesFeature flags (v2.1.205+)

capabilities values: interrupt_receipt_v1, interrupt_cancel_queued_v1 (v2.1.219+), sdk_mcp_manifests and sdk_mcp_tools_list_changed (v2.1.286+; the latter means a tools/list_changed from an SDK MCP server re-lists its tools), plus mcp_read_resource_v1 and mcp_tool_ui_meta_v1 for MCP Apps. Treat it as an open set and feature-detect.

Other common messages

TypeFields and notes
SDKPartialAssistantMessagetype: "stream_event", event (raw BetaRawMessageStreamEvent), parent_tool_use_id (always null: main session only), ttft_ms on message_start, plus reply-matching fields on the first non-ping event
SDKCompactBoundaryMessagecompact_metadata: { trigger: "manual" | "auto"; pre_tokens }
SDKStatusMessagestatus: "compacting" | null, permissionMode?
SDKInformationalMessagecontent, level (info, notice, suggestion, warning), tool_use_id?, prevent_continuation?. Render as plain text. From v2.1.227 hook systemMessage can arrive here, prefixed like PostToolUse:Bash says:
SDKWorkerShuttingDownMessagereason such as host_exit; act on it only when live, as resumed sessions replay it
SDKPluginInstallMessageWith CLAUDE_CODE_SYNC_PLUGIN_INSTALL: status (started, installed, failed, completed), name?, error?
SDKToolUseSummaryMessagesummary, preceding_tool_use_ids
SDKAuthStatusMessageisAuthenticating, output, error?
SDKFilesPersistedEventfiles: { filename, file_id }[], failed, processed_at
SDKPromptSuggestionMessagesuggestion
SDKLocalCommandOutputMessageNever emitted; command output such as /usage arrives as an assistant message

SDKPermissionDeniedMessage

tool_name, tool_use_id, agent_id?, decision_reason_type? (rule, mode, classifier, asyncAgent...), decision_reason?, message (what the model was told).

What it reports depends on configuration:

SetupReported denials
canUseTool with permissionPrompts: 'host'Only those Claude Code decides itself
No callback and no prompt toolThose plus every call that would have prompted (unless a PermissionRequest hook allowed it). From v2.1.223
MCP prompt tool with 'host'None at all
permissionPrompts: 'none'Self-decided plus would-have-prompted (v2.1.259+)

Denials on the PreToolUse hook path are never reported here, and emission is best-effort. Use the result's permission_denials as the record.

SDKContextUsage

Attached to the reply to a /context prompt. Snake_case, without display fields.

FieldMeaning
modelMain-loop model
total_tokensEstimate in use; can exceed the window
raw_max_tokensModel window or the lower auto-compact window (for example the 200K boundary some 1M models get)
percentageCan exceed 100
over_limit?{ tokens_over, kind }; kind is hard_limit (believed model limit) or compaction_window (policy window)
categoriesSDKContextUsageCategory[]: { name, tokens, kind } with kind used, free, buffer (compaction reserve) or deferred (held-out tool schemas)
mcp_tools{ name, server_name, tokens }
memory_files{ path, type, tokens }, type such as Project or User
agentsCustom subagents only, { agent_type, source, tokens }
skills?{ name, source, plugin_name?, tokens }

The type grows additively; ignore unknown fields.

SDKMessageOrigin

Where a user-role message came from. Forwarded onto the result.

kindMeaning
humanTyped by the end user. Set it explicitly
channelArrived on a channel; server names the MCP server
peerFrom a teammate or another of your sessions
task-notificationSynthetic turn, such as a finished background task; optional subkind and fireReason
coordinatorFrom an agent team coordinator
auto-continuationSession continued without user input
unclassifiedClaude Code could not classify a synthetic message (v2.1.223+). Do not set it yourself

Task-notification subkinds (v2.1.213+), set when Anthropic servers verified the source or when you declare a scheduled run:

  • scheduled-trigger: a routine fired (schedule, API, GitHub or Run now), or your declared scheduled run. Framed to the model as its assigned task.
  • peer-send-message: a verified send_message from another cloud session in the same private group (v2.1.224+). Not the cross-session SendMessage tool, whose messages are kind: "peer".

fireReason is a short token such as scheduled, manual, retry, catch_up or api (SDK v0.3.280+).

Declaring your own scheduled runs (SDK v0.3.280+): start the session with CLAUDE_CODE_HOST_SCHEDULED_RUN=1 in env, then send the run's message with origin: { kind: "task-notification", subkind: "scheduled-trigger", fireReason: "scheduled" } and without isSynthetic. Ignored without that variable, or if the environment has CLAUDECODE or CLAUDE_CODE_CHILD_SESSION. fireReason must be 1 to 32 lowercase letters or underscores.

Peer fields:

FieldMeaning
fromTeammate name or sender address; "unknown" for one-way cross-machine messages. Sender-authored, forgeable
fromModebypass or prompting, declared by a relaying host (SDK v0.3.234+)
nameNormalised display name, max 64 code points (v2.1.205+)
fromSessionSender's openable session ID, for navigation only (v2.1.216+)
senderTaskIdTeammate's task ID
bodyDecoded body, exactly what the model sees (v2.1.205+)
verifiedPeerPidKernel-verified PID of the socket peer (v2.1.216+). Use this, not from, for identity. Absent means unverified; for relays it identifies the relay; PIDs recycle

Background task messages

A task is a backgrounded Bash command, a Monitor watch, a subagent or a remote agent.

MessageKey fields
SDKTaskStartedMessagetask_id, tool_use_id?, description, task_type (local_bash, local_agent, remote_agent), is_backgrounded?, spawn_depth? (agents only; 1 = spawned by main thread), ambient?
SDKTaskProgressMessagetask_id, description, subagent_type?, usage (total_tokens, tool_uses, duration_ms), last_tool_name?, summary?
SDKTaskUpdatedMessagetask_id, patch (status pending/running/completed/failed/killed, description, end_time in epoch ms, total_paused_ms, error, is_backgrounded)
SDKTaskNotificationMessagetask_id, tool_use_id?, status (completed, failed, stopped), output_file, summary, ambient?, usage?, resource_links?
SDKBackgroundTasksChangedMessagetasks: full live set of { task_id, task_type, subagent_type?, description, ambient? } (v2.1.203+; subagent_type SDK v0.3.293+)

Notes:

  • ambient: true marks tasks that are not part of the session's work (Claude Code's own, and live-update watchers). Exclude them from activity indicators (SDK v0.3.247+).
  • Resumed subagents always report is_backgrounded: true. A foreground task that later moves to the background reports it via task_updated.
  • summary on progress is model-generated for subagents only with agentProgressSummaries; for backgrounded MCP calls it is the server's own progress.
  • Backgrounded MCP calls deliver their real result in the notification, with resource_links (SDK v0.3.257+). Match by tool_use_id.
  • Every task notification sent to the model carries a "no human input" notice, except scheduled-trigger deliveries. Detect task-notification turns by origin.kind, not the notice text.
  • For background_tasks_changed, replace your cached set with each payload; nothing is sent at start-up, so reset to empty when the process (re)starts. A repeated initialize (such as reinitialize()) is followed by a snapshot from SDK v0.3.239.

Progress and state messages

MessageNotes
SDKToolProgressMessagetool_use_id, tool_name, parent_tool_use_id, elapsed_time_seconds, task_id?, heartbeat?, subagent_type?, subagent_retry?. Main-conversation tools emit a heartbeat every 30 seconds (SDK v0.3.214+; foreground Agent calls from v2.1.257). subagent_retry (agent_id, attempt, max_retries, retry_delay_ms, error_status, error_category) appears per retry while a subagent waits out an API error. Track it by parent_tool_use_id, clear it on the next non-heartbeat progress without subagent_retry or on the tool result, and treat error_category (rate_limit, overloaded, authentication_failed, server_error, cloud_credential_error, unknown) as an open set
SDKThinkingTokensMessageestimated_tokens, estimated_tokens_delta, user_message_uuid?. For progress display; final counts come from the result (v2.1.153+)
SDKSessionStateChangedMessageWith CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1: state is running, idle or requires_action. May repeat; idle and result can arrive in either order. CLAUDE_CODE_BG_TASKS_REPORT_RUNNING controls whether idle waits for background work
SDKCommandsChangedMessageFull new commands list; also fires when MCP prompts join or leave (v2.1.281+)
SDKHookStartedMessagehook_id, hook_name, hook_event
SDKHookProgressMessageAdds stdout, stderr, output
SDKHookResponseMessageAdds exit_code? and outcome (success, error, cancelled)

Hook lifecycle messages are delivered live, including during start-up hooks (they were batched from v2.1.169 to v2.1.203).

SDKRateLimitEvent

rate_limit_info: status (allowed, allowed_warning, rejected), resetsAt?, utilization?, and for exhausted claude.ai subscriptions errorCode: "credits_required" with canUserPurchaseCredits and hasChargeableSavedPaymentMethod (v2.1.181+).

SDKConversationResetMessage

new_conversation_id, uuid, session_id, and from v2.1.281 trigger? (clear, plan_mode_exit, fresh_session, onboarding), user_message_uuid? and timestamp?. In query() only /clear and its aliases produce it. Start an empty transcript under the new ID and drop any cached title, whatever the trigger. Declared in typings from v2.1.203.

AbortError

class AbortError extends Error {}. The only typed error class. Other failures (process exits, launch failures) reject iteration with untyped errors; see troubleshooting.

Hooks

Guide: hooks in the SDK.

type HookCallback = (input: HookInput, toolUseID: string | undefined, options: { signal: AbortSignal }) => Promise<HookJSONOutput>;

interface HookCallbackMatcher {
  matcher?: string;
  hooks: HookCallback[];
  timeout?: number;   // seconds, for every hook in this matcher
}

Base input

Every input has session_id, transcript_path, cwd, and optionally prompt_id (UUID of the prompt being processed, matching OpenTelemetry prompt.id; v2.1.196+), permission_mode, effort: { level }, agent_id and agent_type.

Inputs by event

hook_event_nameExtra fields
PreToolUsetool_name, tool_input, tool_use_id, mcp_server?
PostToolUsetool_name, tool_input, tool_response, tool_use_id, duration_ms?, mcp_server?
PostToolUseFailuretool_name, tool_input, tool_use_id, error, is_interrupt?, duration_ms?, mcp_server?
PostToolBatchtool_calls: { tool_name, tool_input, tool_use_id, tool_response? }[], where tool_response is the serialised result the model sees
PermissionRequesttool_name, tool_input, permission_suggestions?, mcp_server?
PermissionDeniedtool_name, tool_input, tool_use_id, reason, mcp_server?
Notificationmessage, title?, notification_type
UserPromptSubmitprompt, session_title?
UserPromptExpansionexpansion_type (slash_command, mcp_prompt), command_name, command_args, command_source?, prompt
SessionStartsource (startup, resume, clear, compact, fork), agent_type?, model?, session_title?
SessionEndreason (an ExitReason)
Stopstop_hook_active, last_assistant_message?, background_tasks?, session_crons?
StopFailureerror (SDKAssistantMessageError), error_details?, last_assistant_message?
SubagentStartagent_id, agent_type
SubagentStopstop_hook_active, agent_id, agent_transcript_path, agent_type, last_assistant_message?, background_tasks?, session_crons?
PreCompacttrigger (manual, auto), custom_instructions
PostCompacttrigger, compact_summary
PreModelSwitchfrom_model, to_model, requested_model, source (command, picker, sdk), context_tokens, prompt_cache_warm, cache_ttl (5m, 1h), estimated_cache_write_usd, pricing (configured, catalog, default)
PostModelSwitchSame, with source also auto or resume
Setuptrigger (init, maintenance)
TeammateIdleteammate_name, team_name (deprecated)
TaskCreated, TaskCompletedtask_id, task_subject, task_description?, teammate_name?, team_name? (deprecated)
Elicitationmcp_server_name, message, mode? (form, url), url?, elicitation_id?, requested_schema?
ElicitationResultmcp_server_name, elicitation_id?, mode?, action (accept, decline, cancel), content?
ConfigChangesource (user_settings, project_settings, local_settings, policy_settings, skills), file_path?
InstructionsLoadedfile_path, memory_type (User, Project, Local, Managed), load_reason (session_start, nested_traversal, path_glob_match, include, compact), globs?, trigger_file_path?, parent_file_path?
DirectoryAddeddirectory (absolute), source (slash_command for /add-dir, register_repo_root for the SDK control request)
WorktreeCreatename
WorktreeRemoveworktree_path
CwdChangedold_cwd, new_cwd
FileChangedfile_path, event (change, add, unlink)
MessageDisplayturn_id, message_id, index, final, delta

BackgroundTaskSummary is { id, type, status, description, command?, agent_type?, server?, tool?, name? }; SessionCronSummary is { id, schedule, recurring, prompt }. mcp_server (an McpServerProvenance) needs SDK v0.3.274.

Outputs

HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput. The async form is { async: true; asyncTimeout?: number }.

Top-level sync fields: continue, suppressOutput, stopReason, decision (approve or block), systemMessage, reason, terminalSequence (an allowed OSC or BEL sequence for the interactive CLI; the SDK ignores it) and hookSpecificOutput.

hookEventNamehookSpecificOutput fields
PreToolUsepermissionDecision (allow, deny, ask, defer), permissionDecisionReason, updatedInput, additionalContext
PostToolUseadditionalContext, classifierContext (max 2,000 characters shared across hooks, sync only, never copy untrusted output into it), updatedToolOutput, updatedMCPToolOutput (deprecated)
PostToolUseFailure, PostToolBatch, Stop, SubagentStop, SubagentStart, Notification, Setup, UserPromptExpansionadditionalContext
UserPromptSubmitadditionalContext, sessionTitle, suppressOriginalPrompt (omit the prompt from a block message)
SessionStartadditionalContext, initialUserMessage, sessionTitle, watchPaths, reloadSkills (rescan skills after the hook)
PreModelSwitchpermissionDecision (allow, deny, ask; ask is a refusal everywhere except interactive /model), permissionDecisionReason
PostModelSwitchadditionalContext, delivered with the new model's next request
PermissionDeniedretry
PermissionRequestdecision: { behavior: "allow", updatedInput?, updatedPermissions? } or { behavior: "deny", message?, interrupt? }
Elicitation, ElicitationResultaction, content
CwdChanged, FileChangedwatchPaths
WorktreeCreateworktreePath (required)
MessageDisplaydisplayContent to show instead of the delta

Tool input types

Exported from @anthropic-ai/claude-agent-sdk/sdk-tools as the union ToolInputSchemas (and ToolOutputSchemas for outputs).

ToolInput typeFields
ReadFileReadInputfile_path, offset?, limit?, pages? (PDF range such as "1-5")
WriteFileWriteInputfile_path, content
EditFileEditInputfile_path, old_string, new_string, replace_all?
NotebookEditNotebookEditInputnotebook_path, cell_id?, new_source, cell_type?, edit_mode? (replace, insert, delete)
GlobGlobInputpattern, path?
GrepGrepInputpattern, path?, glob?, type?, output_mode?, -i, -o (needs content mode), -n, -B, -A, -C, context?, head_limit?, offset?, multiline?

For PDFs, the Read tool_result carries a summary text block plus a document block (pdf output) or one block per page (parts output). Before SDK v0.3.242 PDF contents came in a separate user message.

Shell and background work

ToolFields
Bashcommand, timeout? (ms; foreground capped at 600000 by default; with run_in_background on v2.1.285+ it is the background limit, default 1800000, max 7200000 unless raised), description?, run_in_background?, dangerouslyDisableSandbox?. The working directory persists across commands; exported variables do not
Monitordescription, timeout_ms (default 300000, max 3600000, effective max 1800000; typed required but defaulted), and exactly one of command or ws: { url, protocols? } (v2.1.195+)
TaskStoptask_id? (also a teammate or named background agent from v2.1.198), shell_id? (deprecated)

TaskOutput was removed in v2.1.277; deny rules naming it are silently ignored. The experimental REPL tool was removed in v2.1.275.

Web

ToolFields
WebFetchurl, prompt
WebSearchquery, allowed_domains?, blocked_domains?

Agents, workflows and planning

ToolFields
Agent (alias Task)description, prompt, subagent_type?, model? (sonnet, opus, haiku, fable), run_in_background?, name?, isolation? (worktree, remote), team_name? and mode? (both deprecated and ignored; mode from v2.1.212)
WorkflowAt least one of script (inline, starting export const meta = { name, description }, using agent(), parallel(), pipeline(), phase(); optional phases in meta), name (built-in or .claude/workflows/), scriptPath (wins over the others). Plus args (any JSON, exposed as global args), resumeFromRunId (same session only). title and description are ignored. SDK v0.3.149+
EnterPlanModenone
ExitPlanModeallowedPrompts? (deprecated, ignored since v2.1.205)
EnterWorktreename? or path? (mutually exclusive). path must be a registered worktree of the repo (or nested repo) on first entry, or under .claude/worktrees/ from inside a worktree session
ExitWorktreeaction (keep, remove), discard_changes? (required true to remove with uncommitted or unmerged work)
AskUserQuestionquestions (each question, header, options[] of label, description, preview?, and multiSelect), answers?, annotations? (preview, notes), metadata?
ReportFindingslevel?, findings[]: file, line?, summary, failure_scenario, short_summary? (60 chars, v2.1.212+), category? (v2.1.199+), verdict? (CONFIRMED, PLAUSIBLE), outcome? (fixed, skipped, no_change_needed). Max 32, most severe first (v2.1.196+)

Tasks

ToolFields
TodoWritetodos[]: content, status, activeForm
TaskCreatesubject, description, activeForm?, metadata?
TaskUpdatetaskId, status? (adds deleted), subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata?
TaskGettaskId
TaskListnone

On by default only for Claude 3.x, Opus 4 to 4.7, Sonnet 4 to 4.6 and Haiku 4.5 (Claude Code v2.1.268+); opt in elsewhere as in todo tracking.

Scheduling and notifications

ToolFields
CronCreatecron (5-field, local time), prompt, recurring? (false fires once), durable? (persist to .claude/scheduled_tasks.json where supported; check the output's durable)
CronDeleteid
CronListnone
ScheduleWakeupdelaySeconds (clamped 60 to 3600), reason, prompt, noop (all required unless stop), stop? (ends a self-paced /loop; v2.1.202+). Backs /loop
RemoteTriggeraction (list, get, create, update, run, create_webhook_trigger (v2.1.225+), list_runs, get_run_log (v2.1.227+)), trigger_id?, session_id?, cursor?, body?. Backs /schedule; only with a claude.ai plan that has routines and cloud sessions allowed
PushNotificationmessage (keep under 200 chars), status: "proactive". Not available on Bedrock, Claude Platform on AWS, Agent Platform or Foundry

MCP and claude.ai

ToolFields
ListMcpResourcesToolserver?
ReadMcpResourceToolserver, uri
ReadMcpResourceDirToolserver, uri. Non-recursive; needs server support and session enablement, otherwise empty resources with an error
RefreshMcpToolsserver?. Only registered with CLAUDE_CODE_ENABLE_REFRESH_MCP_TOOLS=1 and at least one MCP server (v2.1.211+)
mcp__<server>__<tool>McpInput: an open object defined by the server
Artifactaction? (publish default, or list), file_path (required to publish), icon, favicon (deprecated), title, description, label, url (update in place), force (discard a newer version; only on explicit request), capabilities ({} clears; omit to keep; SDK v0.3.235+), contract (latest or a version; SDK v0.3.235+), limit, scope (mine, shared, all). Off by default in SDK sessions and unavailable with API-key auth
Projectsmethod (project_info, project_read, project_search, project_write, project_delete), path?, content? or local_path? (exactly one for writes), present_to_user?, query?, n? (default 5)
ShowOnboardingRolePickernone; blocks until the user responds

Tool output types

Files and search

ToolOutput
ReadDiscriminated on type: text (filePath, content, numLines, startLine, totalLines, truncatedByTokenCap?), image (base64, MIME type, originalSize, dimensions?), notebook (filePath, cells), pdf (filePath, base64, originalSize), parts (filePath, originalSize, count, outputDir, plus firstPage?; pages is in-process only), file_unchanged (filePath, source?: "seeded" when the earlier copy was a start-up CLAUDE.md or memory file)
Writetype (create, update), filePath, content, structuredPatch, originalFile, gitDiff?, userModified?. New files: originalFile null, empty patch. Previous content over about 10 MB: diff skipped. Patch also empty when nothing changed or the diff timed out
EditfilePath, oldString, newString, originalFile, structuredPatch, userModified, replaceAll, gitDiff?
NotebookEditnew_source, old_source?, cell_id?, cell_type, language, edit_mode, error?, notebook_path, original_file, updated_file
GlobdurationMs, numFiles, filenames (sorted by modification time), truncated, totalMatches?, countIsComplete? (v2.1.191+; a lower bound when incomplete)
Grepmode?, numFiles, filenames, content?, numLines?, numMatches?, totalFiles? (v2.1.208+), totalLines? (v2.1.210+), appliedLimit?, appliedOffset?. Count-mode totals cover the full result set

structuredPatch hunks have oldStart, oldLines, newStart, newLines, lines. gitDiff has filename, status (modified, added), additions, deletions, changes, patch, repository?.

Shell

BashOutput: stdout (stdout and stderr interleaved), stderr (the tool's own notices), interrupted, plus optional rawOutputPath, isImage, backgroundTaskId, backgroundedByUser, timedOutAfterMs and backgroundCwdHint (v2.1.210+), backgroundEndsWithFinalResponse (true when a foreground subagent owns the command, so it ends with that subagent; v2.1.227+), dangerouslyDisableSandbox, returnCodeInterpretation, noOutputExpected, structuredContent, persistedOutputPath, persistedOutputSize, staleReadFileStateHint, ghRateLimitHint, and gitOperation:

gitOperation keyShape
commit{ sha, kind: committed | amended | cherry-picked, branch? } (branch omitted on detached HEAD; SDK v0.3.227+)
push{ branch }
branch{ ref, action: merged | rebased }
pr{ number, url?, action }, action one of created, edited, merged, commented, closed, reopened (SDK v0.3.234+), ready, draft, auto-merge-enabled, auto-merge-disabled

MonitorOutput: taskId (use with TaskStop), timeoutMs, persistent?. TaskStopOutput: message, task_id, task_type, command?.

Web

ToolOutput
WebFetchbytes, code, codeText, result, durationMs, url, artifactRead? (slug, ver?, seeded?: false; internal bookkeeping for artifact reads)
WebSearchquery, results (strings or { tool_use_id, content: { title, url }[] }), durationSeconds, searchCount?

Agents and workflows

AgentOutput, discriminated on status:

  • completed: agentId, agentType?, content (text blocks with citations?), resolvedModel? (v2.1.174+), modelsUsed? (only after a mid-run swap; v2.1.212+), totalToolUseCount, totalDurationMs, totalTokens, usage, toolStats? (readCount, searchCount, bashCount, editFileCount, linesAdded, linesRemoved, otherToolCount, frameCount?), prompt, worktreePath?, worktreeBranch?.
  • async_launched: isAsync?, agentId, description, resolvedModel? (model when backgrounded), modelsUsed?, prompt, outputFile, canReadOutputFile?.
  • remote_launched: taskId, sessionUrl, description, prompt, outputFile.

usage and totalTokens come from the subagent's final request only. usage includes service_tier, cache_creation, server_tool_use, inference_geo, speed, iterations, output_tokens_details (SDK v0.3.228+; guard every level, e.g. usage.output_tokens_details?.thinking_tokens ?? 0) and fallback_credit (SDK v0.3.285+). The type was narrower before v2.1.207, so older results may lack optional fields.

WorkflowOutput returns as soon as the run is accepted; the result arrives later as a task completion.

FieldMeaning
statusasync_launched (in-process) or remote_launched (cloud session)
taskId, taskTypeBackground task (local_workflow or remote_agent)
workflowName, summaryFrom the script's meta
runIdPass as resumeFromRunId later; absent for remote runs
transcriptDirWhere subagent transcripts go
scriptPathPersisted script; edit and re-run with it
sessionUrlRemote runs only
warningNon-blocking advice
errorSyntax check failed: the run did not start despite the launched status

Planning, worktrees and questions

ToolOutput
AskUserQuestionquestions, answers (question to answer; multi-select comma-joined), response? (free-form reply), annotations?, afkTimeoutMs?
ExitPlanModeplan, isAgent, filePath?, hasTaskTool?, planWasEdited?, awaitingLeaderApproval?, requestId?
EnterPlanModemessage
EnterWorktreeworktreePath, worktreeBranch?, message
ExitWorktreeaction, originalCwd, worktreePath, worktreeBranch?, tmuxSessionName?, discardedFiles?, discardedCommits?, message
ReportFindingscount, level?, findings echoed back

Tasks, scheduling and notifications

ToolOutput
TodoWriteoldTodos, newTodos
TaskCreatetask: { id, subject }
TaskUpdatesuccess, taskId, updatedFields, error?, statusChange?
TaskGettask (id, subject, description, status, blocks, blockedBy) or null
TaskListtasks[]: id, subject, status, owner?, blockedBy
CronCreateid, humanSchedule, recurring, durable?
CronDeleteid
CronListjobs[]: id, cron, humanSchedule, prompt, recurring?, durable? (false for session-only; absent for jobs read from disk)
ScheduleWakeupscheduledFor (epoch ms), clampedDelaySeconds, wasClamped, stopped? (v2.1.202+), cancelledWakeups? (v2.1.206+; a recurring /loop cron is not cancelled)
RemoteTriggerstatus, json, summary?
PushNotificationmessage, pushSent?, localSent?, disabledReason? (config_off, user_present, no_transport), sentAt?

MCP and claude.ai

ToolOutput
ListMcpResourcesToolArray of uri, name, mimeType?, description?, server
ReadMcpResourceToolcontents[] (uri, mimeType?, text?, blobSavedTo?), error?
ReadMcpResourceDirToolresources[] (uri, name, mimeType?; directories are inode/directory), error?
RefreshMcpToolsArray of server, status (refreshed, error, not_connected), toolCount?, added?, removed?, error?
ArtifactPublish: url, path, title?, version?, capabilities?, stored?, warnings?, contract?, updated?, liveSubscription?. List: artifacts[] (title, url, updatedAt?, rel?), truncated?, scope?
ProjectsDiscriminated on method. project_read inlines small docs in content and writes larger ones to local_file; project_search returns hits with rag: true or falls back to a docs list
ShowOnboardingRolePickerrole?, dismissed?; empty means approved without choosing
MCP toolsMcpOutput: a string or an array of content blocks (the plain-object branch in the type is a schema artefact; may also be undefined at runtime)

Permission types

type PermissionUpdate =
  | { type: "addRules" | "replaceRules" | "removeRules"; rules: PermissionRuleValue[]; behavior: PermissionBehavior; destination: PermissionUpdateDestination }
  | { type: "setMode"; mode: PermissionMode; destination: PermissionUpdateDestination }
  | { type: "addDirectories" | "removeDirectories"; directories: string[]; destination: PermissionUpdateDestination };

type PermissionBehavior = "allow" | "deny" | "ask";
type PermissionUpdateDestination = "userSettings" | "projectSettings" | "localSettings" | "session" | "cliArg";
type PermissionRuleValue = { toolName: string; ruleContent?: string };

Other types

Account, models and agents

TypeFields
AccountInfoemail?, organization?, subscriptionType?, tokenSource?, apiKeySource?
ModelInfovalue, resolvedModel? (what an alias resolves to; v2.1.197+), displayName, description, supportsEffort?, supportedEffortLevels?, supportsAdaptiveThinking?, supportsFastMode?, supportsAutoMode?
AgentInfoname (e.g. Explore, general-purpose), description, model?
SlashCommandname, description, argumentHint, aliases?, builtin? (true for Claude Code's own commands not replaced by name; SDK v0.3.277+)
ConfigScope"local" | "user" | "project"

ApiKeySource reports one of four values: ANTHROPIC_API_KEY, apiKeyHelper, /login managed key (Claude Console login) or none (claude.ai login, bearer token or cloud provider). The type also keeps user, project, org, temporary and oauth for compatibility, but they are not reported.

SdkBeta is "context-1m-2025-08-07". It is retired for Sonnet 4.5 and Sonnet 4 on the Claude API, and over-200K requests then fail. For 1M context, use a model that has it by default such as claude-sonnet-5-5 or claude-opus-5-5, or a [1m] variant such as claude-opus-4-6[1m].

MCP status and provenance

McpServerStatus: name, status (connected, failed, needs-auth, pending, disabled), serverInfo?, error?, config? (an McpServerStatusConfig, any transport plus claudeai-proxy), scope?, source? (SDK v0.3.274+), tools? (name, description?, annotations? with readOnly, destructive, openWorld, and _meta?). Tool _meta carries only the MCP Apps ui object (resourceUri, visibility) and the deprecated ui/resourceUri, when init capabilities include mcp_tool_ui_meta_v1 (SDK v0.3.280+).

McpServerProvenance: { name, source }, carried as mcp_server on tool hook inputs and mcpServer in canUseTool options. source is sdk (only your app can register these), plugin (name is plugin:<plugin>:<server>), or a scope: user, project (.mcp.json), local, dynamic (your mcpServers option, non-SDK), managed, enterprise, claudeai or agent. Base trust on source, never on the name, and escape names from non-sdk sources.

McpSetServersResult: added, removed, errors. Rules for setMcpServers():

  • Servers you omit: outside cloud sessions, servers added by earlier setMcpServers() calls and in-process SDK servers are disconnected and listed in removed. Servers from the mcpServers option (stdio, HTTP, SSE), settings files and plugins keep running.
  • Servers you name: an earlier setMcpServers() stdio, HTTP or SSE server is replaced only if its config changed. An in-process SDK server under that name stays; to swap it, omit it in one call and add it in the next.
  • Built-in servers started by the CLI are dropped and reported in errors.
  • The promise resolves after new servers connect or fail. A failed server appears in both added and errors and as failed in status (from v2.1.257).

SDKMcpResourceLink: uri, name, title?, description?, mimeType?, size?, annotations?. Blocks without string uri and name are dropped (SDK v0.3.257+).

CallToolResult: content (blocks of text, image, audio, resource, resource_link), structuredContent?, isError?.

Usage

ModelUsage: inputTokens, outputTokens, thinkingTokens? (already in outputTokens; SDK v0.3.257+), cacheReadInputTokens, cacheCreationInputTokens, webSearchRequests, costUSD, contextWindow, maxOutputTokens, canonicalModel? and provider? (v2.1.218+; providers firstParty, bedrock, vertex, foundry, anthropicAws, mantle, gateway), costBasis? (list, managed, unknown; v2.1.246+).

Usage is BetaUsage from @anthropic-ai/sdk: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, cache_creation (5m and 1h), server_tool_use, service_tier, speed (standard, fast), inference_geo, iterations, output_tokens_details (thinking_tokens; SDK v0.3.228+), fallback_credit (present if your @anthropic-ai/sdk is 0.115.0+). NonNullableUsage makes every field non-null except fallback_credit.

On output_tokens_details.thinking_tokens: use it for observability, not billing (output_tokens is authoritative); it counts raw reasoning including delimiters and may differ by a few tokens; on streamed assistant messages it is a placeholder; on the result it reads 0 when no breakdown is reported; the object is null on synthesised messages such as API errors.

Thinking

type ThinkingConfig =
  | { type: "adaptive"; display?: "summarized" | "omitted" }
  | { type: "enabled"; budgetTokens?: number; display?: "summarized" | "omitted" }
  | { type: "disabled" };

Adaptive needs Opus 4.6 or later. On Opus 4.7+ the API defaults display to omitted; set summarized to get text. Bedrock and Google Cloud's Agent Platform do not receive display, so Opus 4.7+ thinking blocks are empty there.

Custom process spawning

SpawnOptions: command, args, cwd?, env, signal. SpawnedProcess: stdin, stdout, killed, exitCode, kill(signal), and on/once/off for exit and error; a Node ChildProcess already satisfies it.

The signal does not fire the instant your abortController aborts: the SDK closes stdin, waits about two seconds for a clean shutdown, then aborts it. Listen on your own abortController.signal to react immediately.

import { spawn } from "node:child_process";

const options = {
  spawnClaudeCodeProcess: ({ command, args, env, signal }) =>
    spawn("docker", ["exec", "-i", "agent-box", command, ...args], { env, signal, stdio: ["pipe", "pipe", "inherit"] })
};

RewindFilesResult

canRewind, error?, filesChanged?, insertions?, deletions?, skippedLinks? (paths refused for link safety; v2.1.216+; never set on dryRun).

Sandbox

SandboxSettings

KeyDefaultMeaning
enabledfalseSandbox Bash
failIfUnavailabletrueStop at start-up if the sandbox cannot start; false falls back to unsandboxed with a warning
autoAllowBashIfSandboxedtrueAuto-approve sandboxed Bash
excludedCommands[]Always run unsandboxed, e.g. ['docker *'], with no model involvement
allowUnsandboxedCommandstrueLet the model set dangerouslyDisableSandbox, which then goes through permissions
networknoneSandboxNetworkConfig
filesystemnone{ allowWrite?, denyWrite?, denyRead? } path patterns
ignoreViolationsnoneMap of command substring (or *) to violation-text substrings, e.g. { "*": ["/etc/hosts"] }
enableWeakerNestedSandboxfalseCompatibility mode
ripgrepnone{ command, args? } for a custom ripgrep

Linux needs bubblewrap and socat. If the sandbox cannot start with the default failIfUnavailable, the run ends in error_during_execution with the reason in errors, and a one-shot query() then throws.

SandboxNetworkConfig

Applies to sandboxed Bash only; WebFetch uses permission rules.

KeyMeaning
allowedDomainsReachable domains
deniedDomainsBlocked; beats allowed
strictAllowlistDeny hosts outside the allowlist instead of prompting; only from user, managed or CLI --settings sources (v2.1.219+)
allowManagedDomainsOnlyManaged only; from the SDK pass it via managedSettings
allowLocalBindingBind local ports
allowUnixSocketsAllowed socket paths
allowAllUnixSocketsEvery socket
httpProxyPort, socksProxyPortProxy ports

Warning: Allowing /var/run/docker.sock hands over the host. The proxy filters by hostname without inspecting TLS, so domain fronting can bypass it; see secure deployment.

Gating unsandboxed requests

With allowUnsandboxedCommands on, a Bash call with dangerouslyDisableSandbox: true reaches canUseTool:

for await (const m of query({
  prompt: "Run the end-to-end suite",
  options: {
    sandbox: { enabled: true, allowUnsandboxedCommands: true },
    permissionMode: "default",
    canUseTool: async (tool, input) => {
      if (tool === "Bash" && input.dangerouslyDisableSandbox) {
        const ok = String(input.command).startsWith("npx playwright ");
        return ok
          ? { behavior: "allow", updatedInput: input }
          : { behavior: "deny", message: "Only Playwright may run outside the sandbox" };
      }
      return { behavior: "allow", updatedInput: input };
    }
  }
})) { /* ... */ }

Warning: Never combine bypassPermissions with allowUnsandboxedCommands: the model could leave the sandbox without asking anyone, apart from the actions no mode auto-approves.