
权限系统不是一个开关,而是一条五步单向评估链:Hooks → Deny 规则 → Permission Mode → Allow 规则 → canUseTool 回调,每步都可能终止请求。本篇完整拆解 allowed_tools 与 disallowed_tools 的行为差异(后者才是真正的全局拦截)、bypassPermissions 不受 allowed_tools 约束的关键陷阱、五种 mode 的选型逻辑、set_permission_mode 动态切换、canUseTool 回调的 Python 实现要点,以及 acceptEdits 模式的精确边界,附五条可落地的实践建议。
allow 并不跳过后续步骤——Deny 和 Ask 规则仍会继续评估。disallowed_tools 和 settings.json 中的拒绝规则。命中后即拦截,在任何 permission_mode 下都有效,包括 bypassPermissions。bypassPermissions 在这一步放行所有到达此处的请求;acceptEdits 放行文件操作;其他模式继续向下传递。allowed_tools 和 settings.json 的允许规则。命中后放行。dontAsk 模式下,此步直接跳过并拒绝。disallowed_tools 规则是真正意义上的全局拦截屏障,不受 mode 影响。allowed_tools 和 disallowed_tools 看起来对称,实际上行为逻辑完全不同。
allowed_tools 与 disallowed_tools 的核心差异:前者只做预授权,后者才是真正的拦截屏障 1| 配置写法 | 实际效果 |
|---|---|
allowed_tools=["Read", "Grep"] | Read / Grep 自动放行。未列出的工具仍然存在,继续向下走到 mode 和 canUseTool |
disallowed_tools=["Bash"] | Bash 工具从 Claude 的上下文中整体移除,Claude 看不到这个工具,也无法调用 |
disallowed_tools=["Bash(rm *)"] | Bash 工具保留,但匹配 rm * 的调用被拦截,在所有模式下均有效,包括 bypassPermissions |
allowed_tools 不约束 bypassPermissions。设置了 allowed_tools=["Read"] 同时又用 bypassPermissions 模式,结果是所有工具包括 Bash、Write、Edit 依然会被放行——因为这些工具虽然没在 allow list 里,但会流转到第 3 步被 bypassPermissions 直接批准。1bypassPermissions 模式下屏蔽某些工具,唯一有效的做法是 disallowed_tools,而不是 allowed_tools。auto),覆盖从完全交互到完全自动的全部场景:1| Mode | 工具行为 | 适用场景 |
|---|---|---|
default | 未命中规则的工具触发 canUseTool 回调 | 通用交互场景、需要用户审批的敏感操作 |
dontAsk | 未在 allowed_tools 或规则中的工具直接拒绝,不调用 canUseTool | 无头 Agent,需要固定工具集且不允许任何动态决策 |
acceptEdits | 工作目录内的文件操作(Edit/Write/mkdir/rm/mv 等)自动放行,其他工具照常 | 开发迭代、原型验证、信任 Claude 编辑代码的场景 |
bypassPermissions | 到达第 3 步的所有工具全部放行,无任何提示 | 受控隔离环境,极度信任的自动化流水线 |
plan | 只允许只读工具,Claude 读取代码库并规划,不执行任何修改 | 代码审查、需要在执行前审批变更的场景 |
auto(仅 TS) | 模型分类器逐次判断每个工具调用是否放行 | 详见官方 Auto mode 文档 |
bypassPermissions、acceptEdits 或 auto 时,所有子 Agent 强制继承该模式,无法在子 Agent 级别覆盖。子 Agent 通常有更宽泛的系统 prompt 和更少约束,继承 bypassPermissions 意味着它们拿到了完整的系统访问权限,没有任何审批。allowedTools + dontAsk:1from claude_agent_sdk import query, ClaudeAgentOptions
async for message in query(
prompt="Analyze the codebase structure",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"], # 白名单工具自动放行
permission_mode="dontAsk", # 白名单外一律拒绝,不触发回调
),
):
...dontAsk 直接拒绝,而不是弹出 canUseTool 等待决策。这种组合实现了显式工具表面——Claude 只能用你指定的工具,其他都是硬拒绝。default 观察 Claude 的操作意图,确认没问题后再切换到 acceptEdits:import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
async def main():
async with ClaudeSDKClient(
options=ClaudeAgentOptions(
permission_mode="default", # 开始时要求逐次审批
)
) as client:
await client.query("Help me refactor this module")
# 确认方向后,切换到自动接受文件编辑
await client.set_permission_mode("acceptEdits")
async for message in client.receive_response():
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())set_permission_mode() 的新模式立即生效,后续所有工具请求都按新模式评估。from claude_agent_sdk import ClaudeAgentOptions, query
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, ToolPermissionContext
async def can_use_tool(
tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
if tool_name == "Bash":
cmd = input_data.get("command", "")
print(f"Claude 要执行:{cmd}")
answer = input("允许?(y/n): ")
if answer.lower() == "y":
return PermissionResultAllow(updated_input=input_data)
return PermissionResultDeny(message="用户拒绝了该操作")
return PermissionResultAllow(updated_input=input_data)can_use_tool 必须在流式模式(streaming mode)下使用,且需要同时注册一个 PreToolUse Hook 返回 {"continue_": True}。没有这个 Hook,流会在回调被触发前关闭,回调永远不会执行。2message 字段会直接传给 Claude——Claude 读到后会调整策略,可以利用这个机制主动引导行为,比如写「用户不想删文件,请改成压缩归档」。canUseTool 也负责处理 Claude 的澄清问题(AskUserQuestion 工具),判断方式是检查 tool_name == "AskUserQuestion",然后把问题结构体展示给用户并返回选择结果。acceptEdits 是开发场景里最常用的模式,但它的自动放行有明确边界:1mkdir、touch、rm、rmdir、mv、cp、sedcurl、npm install、git push)additionalDirectories 之外的路径acceptEdits 不是「信任 Claude 做任何事」,是「信任 Claude 在这个目录里改代码」。对 Bash 命令里混入的网络请求、包安装等仍会触发 canUseTool。allowed_tools + dontAsk 锁死工具集,不要依赖 canUseTool 缺席来等效 dontAsk——显式配置比隐式行为更可靠。bypassPermissions 下屏蔽工具,只能用 disallowed_tools,不要用 allowed_tools。前者是从 Claude 的上下文里拔掉工具定义;后者只是预授权,不能限制 bypassPermissions 的放行范围。Bash(rm *) 而不是 Bash 做 Deny 规则,当你需要保留 Bash 能力但禁止特定危险命令时。裸名 Bash 会把整个工具从上下文移除,模式规则 Bash(rm *) 则是在运行时按调用内容拦截。disallowed_tools + Deny 规则。plan 模式做代码审查前的探索阶段。plan 模式允许读取整个代码库、执行只读命令,Claude 可以用 AskUserQuestion 澄清需求,然后给出计划——你审批后再切换模式执行。这是人机协作流程里最安全的起步方式。docs.anthropic.com
docs.anthropic.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.