Python SDK 参考:工具输入输出、示例与沙盒
Agent SDK Python 里内置工具(Agent、Bash、Read、Edit、Grep、Task* 等)的输入输出结构、持续对话界面示例、错误处理示例,以及 SandboxSettings、SandboxNetworkConfig、SandboxIgnoreViolations 与对未沙盒命令的权限回退。
本页是 Python Agent SDK 参考的最后一部分:内置工具的输入输出结构、两个完整示例,以及沙盒配置。
工具输入输出类型
Python SDK 不把这些作为类型导出,但它们代表消息里工具输入和输出的结构。下面每个输出都是你从该工具的 UserMessage.tool_use_result 读到的值,键名与 Claude Code 发出的完全一致;标注为 | None 并带"present when"或"optional"注释的键在不适用时被省略。
Agent
工具名: Agent。旧名 Task 仍作为别名接受,init SystemMessage 里的 tools 列表为向后兼容目前仍把该工具报告为 Task。
输入:
{
"description": str, # 任务的简短(3-5 个词)描述
"prompt": str, # 智能体要执行的任务
"subagent_type": str | None, # 要使用的专用智能体类型
"model": "sonnet" | "opus" | "haiku" | "fable" | None, # 该智能体的模型覆盖
"run_in_background": bool | None, # 智能体默认在后台运行;设为 False 同步运行
"name": str | None, # 派生智能体的名字
"team_name": str | None, # 已弃用;被忽略
"mode": "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan" | None, # 已弃用;被忽略。子智能体的权限模式由子智能体继承规则决定
"isolation": "worktree" | "remote" | None, # 智能体改动的隔离模式
}输出(status:"completed"):
{
"status": "completed",
"agentId": str, # 运行的智能体 ID
"agentType": str | None, # 处理该任务的子智能体类型
"content": [ # 结果内容块
{
"type": "text",
"text": str,
"citations": list | None,
}
],
"resolvedModel": str | None, # 子智能体起始使用的模型
"modelsUsed": list[str] | None, # 按顺序使用的模型,连续重复项被折叠
"totalToolUseCount": int, # 智能体发出的工具调用数
"totalDurationMs": int, # 执行时长(毫秒)
"totalTokens": int, # 来自最后一个 API 请求的 token 数,不是整个运行
"usage": { # token 用量统计
"input_tokens": int,
"output_tokens": int,
"cache_creation_input_tokens": int | None,
"cache_read_input_tokens": int | None,
"server_tool_use": {"web_search_requests": int, "web_fetch_requests": int} | None,
"service_tier": str | None,
"cache_creation": {"ephemeral_1h_input_tokens": int, "ephemeral_5m_input_tokens": int} | None,
"inference_geo": str | None,
"speed": str | None,
"iterations": Any | None,
"output_tokens_details": {"thinking_tokens": int | None} | None,
},
"toolStats": { # 运行的聚合工具活动
"readCount": int,
"searchCount": int,
"bashCount": int,
"editFileCount": int,
"linesAdded": int,
"linesRemoved": int,
"otherToolCount": int,
"frameCount": int | None,
} | None,
"prompt": str, # 智能体运行的提示
"worktreePath": str | None, # Claude Code 保留了子智能体的 worktree 时存在
"worktreeBranch": str | None, # Claude Code 用 git 创建了该 worktree 时存在
}输出(status:"async_launched"):
{
"status": "async_launched",
"isAsync": bool | None, # 后台启动时为 True
"agentId": str, # 启动的智能体 ID
"description": str, # 任务描述
"resolvedModel": str | None, # 转入后台时使用的模型
"modelsUsed": list[str] | None, # 转入后台之前按顺序使用的模型,连续重复项被折叠
"prompt": str, # 智能体运行的提示
"outputFile": str, # 智能体输出写入的文件路径
"canReadOutputFile": bool | None, # 输出文件能否被直接读取
}输出(status:"remote_launched"):
{
"status": "remote_launched",
"taskId": str, # 分发的任务 ID
"sessionUrl": str, # 云端会话链接
"description": str, # 任务描述
"prompt": str, # 智能体运行的提示
"outputFile": str, # 智能体输出写入的文件路径
}返回子智能体的结果,按 status 字段区分:"completed" 表示已完成的任务,"async_launched" 表示后台任务,"remote_launched" 表示 Claude Code 分发到云端会话的任务(sessionUrl 链接到该会话,taskId 标识它)。在 completed 变体上,resolvedModel 点名子智能体起始使用的模型,当 availableModels 或其他覆盖生效时它可能与请求的 model 输入不同(需要 Claude Code v2.1.174 及以上)。Claude Code 从子智能体的最后一个 API 请求而不是整次运行填充 usage 和 totalTokens;存在时,usage 里 output_tokens_details 下的 thinking_tokens 是该请求的输出 token 中属于思考 token 的数量(output_tokens_details 键需要 Python SDK v0.2.136 及以上,它捆绑 Claude Code v2.1.228)。
AskUserQuestion
工具名: AskUserQuestion。在执行期间向用户提出澄清问题(用法见「处理批准和用户输入」)。
输入:
{
"questions": [ # 要问用户的问题(1-4 个)
{
"question": str, # 要问用户的完整问题
"header": str, # 显示为标签的很短的标签(最多 12 字符)
"options": [ # 可用的选项(2-4 个)
{
"label": str, # 该选项的显示文本(1-5 个词)
"description": str, # 该选项含义的说明
"preview": str | None, # 选项获得焦点时渲染的预览内容
}
],
"multiSelect": bool, # 设为 true 允许多选
}
],
"answers": dict[str, str] | None,
# 由权限系统填充的用户回答。多选回答是所选标签以逗号连接的字符串;
# 输入时接受标签列表并强制转换成这种形式
"annotations": dict[str, dict] | None,
# 用户的按问题注解,以问题文本为键。每个值可携带 "preview"
# (所选选项的预览内容)和 "notes"(对所选项的自由文本备注)
"metadata": dict | None, # 分析元数据,如 {"source": "remember"};不向用户显示
}输出:
{
"questions": [ # 所问的问题
{
"question": str,
"header": str,
"options": [{"label": str, "description": str, "preview": str | None}],
"multiSelect": bool,
}
],
"answers": dict[str, str], # 把问题文本映射到回答字符串
# 多选回答以逗号分隔
"response": str | None,
# 用户键入的自由回复而不是回答问题;设置时,
# Claude 收到 "The user responded: ..." 来代替回答列表
"annotations": dict[str, dict] | None, # 来自用户所选项的按问题 "preview" 和 "notes"
"afkTimeoutMs": int | None, # 对话框在用户闲置这么多毫秒后自动解决时设置;用户回答了则不存在
}Bash
工具名: Bash。前台上限由什么决定,见「超时和输出限制」;后台时间限制见「后台命令的时间限制」。
输入:
{
"command": str, # 要执行的命令
"timeout": int | None, # 毫秒。前台:默认上限 600000,更高的值被钳到上限。带 run_in_background(Claude Code v2.1.285 及以上):后台时限,省略时 1800000,上限 7200000(除非调高)
"description": str | None, # 清晰简洁的描述(5-10 个词)
"run_in_background": bool | None, # 设为 true 在后台运行
}输出:
{
"stdout": str, # 命令的输出;stdout 和 stderr 合并成这一个交错的流
"stderr": str, # 工具自己添加的通知,不是命令的 stderr
"interrupted": bool, # 命令是否被中断
"isImage": bool | None, # stdout 是否含图片数据
"backgroundTaskId": str | None, # 命令在后台运行时的后台任务 ID
}Monitor
工具名: Monitor。运行后台来源并把每个事件交给 Claude,使它无需轮询就能作出反应:command 运行脚本并对每行 stdout 发出一个事件,ws 打开 WebSocket 并对每个文本帧发出一个事件;command 和 ws 要恰好提供一个。Monitor 运行命令时遵循与 Bash 相同的权限规则;WebSocket 监视单独提示批准;ws 来源需要 Claude Code v2.1.195 及以上。
输入:
{
"command": str | None, # shell 脚本;每行 stdout 是一个事件,退出即结束监视
"ws": dict | None, # WebSocket 来源:{"url": str, "protocols": list[str] | None};每个文本帧是一个事件
"description": str, # 通知里显示的简短描述
"timeout_ms": int | None, # 截止时间(毫秒)(默认 300000,最大 3600000;有效截止时间最多 1800000)
}输出:
{
"taskId": str, # 后台监视任务的 ID
"timeoutMs": int, # 监视的有效截止时间(毫秒)
"persistent": bool | None, # False:每个监视都有截止时间
}Edit
工具名: Edit
输入:
{
"file_path": str, # 要修改的文件的绝对路径
"old_string": str, # 要替换的文本
"new_string": str, # 用来替换的文本
"replace_all": bool | None, # 替换所有出现(默认 False)
}输出:
{
"filePath": str, # 被编辑的文件
"oldString": str, # 被替换的文本
"newString": str, # 替换它的文本
"originalFile": str | None, # 编辑之前的文件内容
"structuredPatch": [ # 该改动的 diff 块
{
"oldStart": int,
"oldLines": int,
"newStart": int,
"newLines": int,
"lines": list[str],
}
],
"userModified": bool, # 用户是否在接受之前修改了提议的编辑
"replaceAll": bool, # 是否替换了所有出现
"gitDiff": { # 该文件可选的 git diff 摘要
"filename": str,
"status": "modified" | "added",
"additions": int,
"deletions": int,
"changes": int,
"patch": str,
"repository": str | None, # 可用时的 GitHub owner/repo
} | None,
}Read
工具名: Read
输入:
{
"file_path": str, # 要读取的文件的绝对路径
"offset": int | None, # 开始读取的行号
"limit": int | None, # 要读取的行数
}输出取决于 Claude 读了什么,取下列形状之一,检查 type 键来区分。
# type: "text"
{
"type": "text",
"file": {
"filePath": str, # 被读取的文件
"content": str, # 返回的内容
"numLines": int, # 返回内容的行数
"startLine": int, # 内容起始的行号
"totalLines": int, # 文件的总行数
"truncatedByTokenCap": bool | None, # 整文件读取超过 token 上限、content 是第一页时存在且为 True
},
}
# type: "image"
{
"type": "image",
"file": {
"base64": str, # base64 编码的图片数据
"type": "image/jpeg" | "image/png" | "image/gif" | "image/webp", # 图片 MIME 类型
"originalSize": int, # 原始文件字节数
"dimensions": { # 用于坐标映射的可选尺寸信息
"originalWidth": int | None, # 可选;原始宽度(像素)
"originalHeight": int | None, # 可选;原始高度(像素)
"displayWidth": int | None, # 可选;缩放后的宽度
"displayHeight": int | None, # 可选;缩放后的高度
} | None,
},
}
# type: "notebook"
{
"type": "notebook",
"file": {
"filePath": str, # 被读取的 notebook
"cells": list, # notebook 单元格
},
}
# type: "pdf"
{
"type": "pdf",
"file": {
"filePath": str, # 被读取的 PDF
"base64": str, # base64 编码的 PDF 数据
"originalSize": int, # 文件字节数
},
}
# type: "parts"
{
"type": "parts",
"file": {
"filePath": str, # 被读取的 PDF
"originalSize": int, # 文件字节数
"count": int, # 提取为图片的页数
"outputDir": str, # 含提取页面图片的目录
},
"firstPage": int | None, # 可选:第一个提取页的文档页码
}
# type: "file_unchanged"
{
"type": "file_unchanged", # 文件自 Claude 在该会话里上次读取以来没变,所以内容不重复
"file": {
"filePath": str,
},
"source": "seeded" | None, # 较早的副本来自启动时加载的 CLAUDE.md 或记忆文件而不是 Read 调用时存在
}Write
工具名: Write
输入:
{
"file_path": str, # 要写入的文件的绝对路径
"content": str, # 要写入文件的内容
}输出:
{
"type": "create" | "update", # 写入是创建了新文件还是覆盖了现有文件
"filePath": str, # 被写入的文件
"content": str, # 被写入的内容
"structuredPatch": [ # diff 块;新文件、没有变化、或 Claude Code 跳过了 diff 时为空
{
"oldStart": int,
"oldLines": int,
"newStart": int,
"newLines": int,
"lines": list[str],
}
],
"originalFile": str | None, # 先前内容;新文件或先前内容太大无法包含时为 None
"gitDiff": { # 该文件可选的 git diff 摘要
"filename": str,
"status": "modified" | "added",
"additions": int,
"deletions": int,
"changes": int,
"patch": str,
"repository": str | None, # 可用时的 GitHub owner/repo
} | None,
"userModified": bool | None, # 可选;用户是否在接受之前编辑了提议的内容
}Glob
工具名: Glob
输入:
{
"pattern": str, # 用来匹配文件的 glob 模式
"path": str | None, # 搜索的目录(默认 cwd)
}输出:
{
"durationMs": int, # 运行搜索花的时间(毫秒)
"numFiles": int, # 返回的路径数(截断之后)
"filenames": list[str], # 匹配的文件路径
"truncated": bool, # 结果是否在 100 个文件的限制处被截断
"totalMatches": int | None, # 可选:截断前匹配文件的总数;countIsComplete 为 False 时是下限
"countIsComplete": bool | None, # 可选;totalMatches 是否精确
}totalMatches 和 countIsComplete 需要 Claude Code v2.1.191 及以上。
Grep
工具名: Grep
输入:
{
"pattern": str, # 正则表达式模式
"path": str | None, # 搜索的文件或目录
"glob": str | None, # 过滤文件的 glob 模式
"type": str | None, # 要搜索的文件类型
"output_mode": str | None, # "content"、"files_with_matches" 或 "count"
"-i": bool | None, # 不区分大小写的搜索
"-n": bool | None, # 显示行号
"-B": int | None, # 每个匹配前显示的行数
"-A": int | None, # 每个匹配后显示的行数
"-C": int | None, # 前后都显示的行数
"context": int | None, # 前后显示的行数;-C 是它的别名
"-o": bool | None, # 只打印每行的匹配部分
"head_limit": int | None, # 把输出限制在前 N 行/条
"offset": int | None, # 在应用 head_limit 之前跳过前 N 行/条
"multiline": bool | None, # 启用多行模式
}输出:
{
"mode": "content" | "files_with_matches" | "count" | None, # 使用的输出模式
"numFiles": int, # 结果里的文件数;content 模式下总是 0
"filenames": list[str], # files_with_matches 模式下匹配的文件;其他模式为空
"content": str | None, # content 模式下的匹配行,或 count 模式下每文件的计数
"numLines": int | None, # content 里的行数,content 模式下存在
"numMatches": int | None, # 匹配总数,count 模式下存在
"totalFiles": int | None, # 可选:files_with_matches 模式下 head_limit 和 offset 之前的总数
"totalLines": int | None, # 可选:content 模式下 head_limit 和 offset 之前的总数
"appliedLimit": int | None, # head_limit 截断了结果时存在
"appliedOffset": int | None, # 应用了 offset 时存在
}Grep 在每种输出模式下都返回这个字典形状,哪些可选键存在取决于 output_mode;totalFiles 需要 Claude Code v2.1.208 及以上,totalLines 需要 v2.1.210 及以上。
NotebookEdit
工具名: NotebookEdit
输入:
{
"notebook_path": str, # Jupyter notebook 的绝对路径
"cell_id": str | None, # 要编辑的单元格的 ID
"new_source": str, # 该单元格的新源码
"cell_type": "code" | "markdown" | None, # 单元格类型
"edit_mode": "replace" | "insert" | "delete" | None, # 编辑操作类型
}输出:
{
"new_source": str, # 写入单元格的源码
"old_source": str | None, # 先前的单元格源码,replace 和 delete 时存在
"cell_id": str | None, # 被编辑单元格的 ID(可用时)
"cell_type": "code" | "markdown", # 单元格类型
"language": str, # notebook 的编程语言
"edit_mode": str, # 使用的编辑模式
"error": str | None, # 操作失败时的错误消息
"notebook_path": str, # notebook 文件
"original_file": str, # 编辑之前的 notebook 内容
"updated_file": str, # 编辑之后的 notebook 内容
}WebFetch 与 WebSearch
WebFetch 输入: {"url": str, "prompt": str}(要获取内容的 URL 和对获取内容运行的提示)。输出:
{
"bytes": int, # 获取内容的字节数
"code": int, # HTTP 响应码
"codeText": str, # HTTP 响应码文本
"result": str, # 把提示应用到内容上得到的处理结果
"durationMs": int, # 获取并处理内容的时间(毫秒)
"url": str, # 被获取的 URL
}WebSearch 输入:
{
"query": str, # 要使用的搜索查询
"allowed_domains": list[str] | None, # 只包含来自这些域名的结果
"blocked_domains": list[str] | None, # 绝不包含来自这些域名的结果
}输出:
{
"query": str, # 搜索查询
"results": list[str | {"tool_use_id": str, "content": list[{"title": str, "url": str}]}],
"durationSeconds": float, # 搜索时长(秒)
}TodoWrite 与 Task 工具
下列工具默认只在 Claude 3.x 模型、Opus 4 到 4.7、Sonnet 4 到 4.6 和 Haiku 4.5 上可用;在其他每个模型上(包括 Claude Code 不认识的模型 ID),除非选择加入否则不可用:TodoWrite、TaskCreate、TaskGet、TaskUpdate、TaskList。这些工具可用的地方,Claude Code 提供四个 Task 工具,或在你设了 CLAUDE_CODE_ENABLE_TASKS=0 时改为提供 TodoWrite;这个默认集合适用于 Claude Code v2.1.268 及以上。
# TodoWrite 输入
{
"todos": [
{
"content": str, # 任务描述
"status": "pending" | "in_progress" | "completed", # 任务状态
"activeForm": str, # 描述的进行时形式
}
]
}
# TodoWrite 输出
{
"oldTodos": [ # 更新之前的待办列表
{"content": str, "status": "pending" | "in_progress" | "completed", "activeForm": str}
],
"newTodos": [ # 更新之后的待办列表
{"content": str, "status": "pending" | "in_progress" | "completed", "activeForm": str}
],
}
# TaskCreate 输入 / 输出
{
"subject": str, # 简短的任务标题
"description": str, # 详细的任务正文
"activeForm": str | None, # 进行中时显示的现在时标签
"metadata": dict | None, # 任意的调用方元数据
}
{
"task": {"id": str, "subject": str}, # 带分配 ID 的已创建任务
}
# TaskUpdate 输入 / 输出
{
"taskId": str, # 要修补的任务的 ID
"status": Literal["pending", "in_progress", "completed", "deleted"] | None,
"subject": str | None,
"description": str | None,
"activeForm": str | None,
"addBlocks": list[str] | None, # 该任务现在阻塞的任务 ID
"addBlockedBy": list[str] | None, # 现在阻塞该任务的任务 ID
"owner": str | None,
"metadata": dict | None,
}
{
"success": bool,
"taskId": str,
"updatedFields": list[str], # 改变了的字段名
"error": str | None,
"statusChange": {"from": str, "to": str} | None,
}
# TaskGet 输入 / 输出
{"taskId": str} # 要读取的任务的 ID
{
"task": {
"id": str,
"subject": str,
"description": str,
"status": Literal["pending", "in_progress", "completed"],
"blocks": list[str],
"blockedBy": list[str],
} | None, # ID 找不到时为 None
}
# TaskList 输入 / 输出
{}
{
"tasks": [
{
"id": str,
"subject": str,
"status": Literal["pending", "in_progress", "completed"],
"owner": str | None,
"blockedBy": list[str],
}
],
}TaskOutput 与 TaskStop
TaskOutput 在 Claude Code v2.1.277 中被移除(别名 BashOutput 也一并移除):它之前用于取得运行中或已完成后台任务的输出,现在 Claude 改用 Read 读取后台任务的输出文件;仍然点名这两个名字的 disallowed_tools 条目或拒绝规则会被静默忽略。TaskStop(旧名 KillShell 和 KillBash 仍作为别名接受):
# 输入
{
"task_id": str | None, # 要停止的后台任务的 ID
"shell_id": str | None, # 已弃用:改用 task_id
}
# 输出
{
"message": str, # 关于该操作的状态消息
"task_id": str, # 被停止的任务的 ID
"task_type": str, # 被停止的任务的类型
"command": str | None, # 被停止任务的命令或描述
}ExitPlanMode
# 输入
{
"plan": str # 由用户运行以供批准的计划
}
# 输出
{
"plan": str | None, # 呈现给用户的计划
"isAgent": bool, # 子智能体调用该工具时为 True
"filePath": str | None, # 计划被保存到文件时存在
"hasTaskTool": bool | None, # 可选;当前上下文里 Agent 工具是否可用
"planWasEdited": bool | None, # 用户在批准之前编辑了计划时存在且为 True
"awaitingLeaderApproval": bool | None, # 队友把计划发给团队负责人批准时存在且为 True
"requestId": str | None, # 该批准请求的可选 ID
}ListMcpResources 与 ReadMcpResource
ListMcpResourcesTool 的结果是列表而不是字典,所以该工具的 tool_use_result 持有 list。
# ListMcpResourcesTool 输入
{
"server": str | None # 按其过滤资源的可选服务器名
}
# 输出(每个资源一条)
[
{
"uri": str, # 资源 URI
"name": str, # 资源名
"mimeType": str | None, # 可选的 MIME 类型
"description": str | None, # 可选的描述
"server": str, # 提供该资源的服务器
}
]
# ReadMcpResourceTool 输入
{
"server": str, # MCP 服务器名
"uri": str, # 要读取的资源 URI
}
# 输出
{
"contents": [
{
"uri": str, # 资源 URI
"mimeType": str | None, # 可选的 MIME 类型
"text": str | None, # 文本内容,或关于二进制内容的说明
"blobSavedTo": str | None, # Claude Code 把二进制内容保存到磁盘时存在;保存文件的路径
}
],
"error": str | None, # 服务器无法读取该资源时存在
}示例:构建持续对话界面
下面的例子让一个 ClaudeSDKClient 跨轮次保持连接,使 Claude 记住先前的消息。键入 new 断开并重新连接以获得全新会话,键入 exit 结束对话。
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
)
import asyncio
class ConversationSession:
"""与 Claude 维持单个对话会话。"""
def __init__(self, options: ClaudeAgentOptions | None = None):
self.client = ClaudeSDKClient(options)
self.turn_count = 0
async def start(self):
await self.client.connect()
print("Starting conversation session. Claude will remember context.")
print(
"Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session"
)
while True:
user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")
if user_input.lower() == "exit":
break
elif user_input.lower() == "interrupt":
await self.client.interrupt()
print("Task interrupted!")
continue
elif user_input.lower() == "new":
# 断开并重新连接以获得全新会话
await self.client.disconnect()
await self.client.connect()
self.turn_count = 0
print("Started new conversation session (previous context cleared)")
continue
# 发送消息——会话保留所有先前的消息
await self.client.query(user_input)
self.turn_count += 1
# 处理响应
print(f"[Turn {self.turn_count}] Claude: ", end="")
async for message in self.client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text, end="")
print() # 响应之后换行
await self.client.disconnect()
print(f"Conversation ended after {self.turn_count} turns.")
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash"], permission_mode="acceptEdits"
)
session = ConversationSession(options)
await session.start()
asyncio.run(main())示例:错误处理
下面的例子把一个 query() 调用包进 SDK 抛出的四种错误类型的处理程序里。该例捕获 ResultError,它需要 Python Agent SDK 0.2.140 及以上。
import asyncio
from claude_agent_sdk import (
query,
CLINotFoundError,
ProcessError,
ResultError,
CLIJSONDecodeError,
)
async def main():
try:
async for message in query(prompt="Hello"):
print(message)
except CLINotFoundError:
print(
"Claude Code CLI not found. Try reinstalling: pip install --force-reinstall claude-agent-sdk"
)
# 要在它所继承的 ProcessError 之前捕获 ResultError。它的消息
# 携带错误文本。失败的最终请求(如 API 错误)以 subtype "success"
# 到达,所以要先按 terminal_reason 分支。
except ResultError as e:
if e.terminal_reason == "api_error":
print(f"API request failed: {e}")
else:
print(f"Query ended with an error result ({e.terminal_reason or e.subtype}): {e}")
except ProcessError as e:
print(f"Process failed with exit code: {e.exit_code}")
except CLIJSONDecodeError as e:
print(f"Failed to parse response: {e}")
asyncio.run(main())沙盒配置
SandboxSettings
沙盒行为的配置。用它以编程方式启用命令沙盒并配置网络限制。
class SandboxSettings(TypedDict, total=False):
enabled: bool
autoAllowBashIfSandboxed: bool
excludedCommands: list[str]
allowUnsandboxedCommands: bool
network: SandboxNetworkConfig
ignoreViolations: SandboxIgnoreViolations
enableWeakerNestedSandbox: bool| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
enabled | bool | False | 为命令执行启用沙盒模式 |
autoAllowBashIfSandboxed | bool | True | 沙盒启用时自动批准 bash 命令 |
excludedCommands | list[str] | [] | 绕过沙盒限制的命令,如 ["docker *"];这些命令无需模型参与自动不带沙盒运行 |
allowUnsandboxedCommands | bool | True | 允许模型请求在沙盒之外运行命令;为 True 时模型可以在工具输入里设 dangerouslyDisableSandbox,这会回落到权限系统 |
network | SandboxNetworkConfig | None | 沙盒的网络配置 |
ignoreViolations | SandboxIgnoreViolations | None | 配置要忽略哪些沙盒违规 |
enableWeakerNestedSandbox | bool | False | 为兼容性启用较弱的嵌套沙盒 |
注意:沙盒依赖平台支持,在 Linux 上还依赖 bubblewrap 和 socat 这类工具。默认情况下,enabled 为 True 但沙盒无法启动时,命令不带沙盒运行,并在 stderr 上给出警告;这个默认值与 TypeScript SDK 不同,那里 failIfUnavailable 默认为 true。要改为停止,在沙盒设置里设 "failIfUnavailable": True:该键尚未在 SandboxSettings 上声明,但 SDK 会把它转发给 Claude Code,后者会遵从;此时 query() 报告 subtype="error_during_execution" 的 ResultMessage,原因在 errors 里;因为这是单次 query() 调用,SDK 在产出该错误结果之后抛出异常。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
sandbox_settings = {
"enabled": True,
"autoAllowBashIfSandboxed": True,
"failIfUnavailable": True,
"network": {"allowLocalBinding": True},
}
async def main():
try:
async for message in query(
prompt="Build and test my project",
options=ClaudeAgentOptions(sandbox=sandbox_settings),
):
print(message)
except Exception as error:
# 单次 query() 在产出错误结果之后抛出,
# 例如设了 failIfUnavailable 而沙盒无法启动时。
print(f"Session ended with an error: {error}")
asyncio.run(main())Unix socket 安全:allowUnixSockets 选项可以授予对延伸到沙盒之外的系统服务的访问。例如允许 /var/run/docker.sock 实际上通过 Docker API 授予完全的宿主系统访问,绕过沙盒隔离。只允许严格必需的 Unix socket,并理解每个的安全含义。
SandboxNetworkConfig
沙盒模式的网络配置。当父级 SandboxSettings 里 enabled 为 True 时,这些设置适用于沙盒里的 Bash 命令;它们不限制 WebFetch 工具,后者改用权限规则。
class SandboxNetworkConfig(TypedDict, total=False):
allowedDomains: list[str]
deniedDomains: list[str]
allowManagedDomainsOnly: bool
allowUnixSockets: list[str]
allowAllUnixSockets: bool
allowLocalBinding: bool
allowMachLookup: list[str]
httpProxyPort: int
socksProxyPort: int| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
allowedDomains | list[str] | [] | 沙盒进程可访问的域名 |
deniedDomains | list[str] | [] | 沙盒进程不可访问的域名,优先于 allowedDomains |
allowManagedDomainsOnly | bool | False | 仅限托管设置:在托管设置里设置时,忽略来自非托管设置来源的 allowedDomains 和 WebFetch(domain:...) 允许规则;经 SDK 选项设置时无效 |
allowUnixSockets | list[str] | [] | 仅限 macOS:进程可访问的 Unix socket 路径,如 Docker socket;在 Linux 上被忽略 |
allowAllUnixSockets | bool | False | 允许访问所有 Unix socket |
allowLocalBinding | bool | False | 允许进程绑定本地端口(例如用于开发服务器) |
allowMachLookup | list[str] | [] | 仅限 macOS:允许的 XPC/Mach 服务名,支持结尾通配符 |
httpProxyPort | int | None | 网络请求的 HTTP 代理端口 |
socksProxyPort | int | None | 网络请求的 SOCKS 代理端口 |
注意:内置的沙盒代理按请求的主机名强制网络允许列表,不终止也不检查 TLS 流量,所以域前置之类的技术可能绕过它(细节见沙盒安全限制;配置终止 TLS 的代理见「安全部署」)。
SandboxIgnoreViolations
忽略特定沙盒违规的配置。
class SandboxIgnoreViolations(TypedDict, total=False):
file: list[str]
network: list[str]file(list[str],默认 [])是要忽略违规的文件路径模式;network(list[str],默认 [])是要忽略违规的网络模式。
对不带沙盒命令的权限回退
启用 allowUnsandboxedCommands 时,模型可以通过在工具输入里设 dangerouslyDisableSandbox: True 请求在沙盒之外运行命令。这些请求回落到现有的权限系统,也就是会调用你的 can_use_tool 处理程序,让你能实现自定义的授权逻辑。你的 excludedCommands 条目则无需模型参与就把调用移出沙盒。下面的例子记录每个不带沙盒的请求,并拒绝它,除非你自己的授权逻辑允许:
import asyncio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
HookMatcher,
PermissionResultAllow,
PermissionResultDeny,
ToolPermissionContext,
)
def is_command_authorized(command: str | None) -> bool:
# 换成你自己的授权逻辑
return False
async def can_use_tool(
tool: str, input: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
# 检查模型是否在请求绕过沙盒
if tool == "Bash" and input.get("dangerouslyDisableSandbox"):
# 模型请求在沙盒之外运行这条命令
print(f"Unsandboxed command requested: {input.get('command')}")
if is_command_authorized(input.get("command")):
return PermissionResultAllow()
return PermissionResultDeny(
message="Command not authorized for unsandboxed execution"
)
return PermissionResultAllow()
# 必需:用一个假 hook 让流为 can_use_tool 保持打开
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}
async def prompt_stream():
yield {
"type": "user",
"message": {"role": "user", "content": "Deploy my application"},
}
async def main():
async for message in query(
prompt=prompt_stream(),
options=ClaudeAgentOptions(
sandbox={
"enabled": True,
"allowUnsandboxedCommands": True, # 模型可以请求不带沙盒执行
},
permission_mode="default",
can_use_tool=can_use_tool,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
),
):
print(message)
asyncio.run(main())注意:带 dangerouslyDisableSandbox: True 运行的命令拥有完整的系统访问,要确保你的 can_use_tool 处理程序仔细验证这些请求。如果 permission_mode 设为 bypassPermissions 且启用了 allowUnsandboxedCommands,模型可以不经批准提示自主执行沙盒之外的命令(没有任何模式自动批准的动作除外),这种组合实际上让模型静默逃出沙盒隔离。
另见
- SDK 概览:通用的 SDK 概念
- TypeScript SDK 参考:TypeScript SDK 文档
- 自定义工具:定义供 Claude 调用的进程内 MCP 工具
- CLI 参考:命令行界面
- 常见工作流:逐步指南