Claude Code 源码解析 Part 2:权限系统不是“弹窗”,而是多 Agent Runtime 的控制面
上一篇我拆了 Claude Code 的多 Agent 编排,核心结论是:
Claude Code 的多 Agent 不是几个 prompt 互相调用,而是 Coordinator + Task Runtime + Mailbox + Backend 组成的一套运行时。
上一篇链接:
Claude Code 多 Agent 编排总览
但如果只有编排,没有控制,这套系统很快就会失控。
这就引出 Claude Code 另一个更关键、也更容易被低估的模块:
权限系统。
很多人一提权限,会先想到“执行 Bash 之前弹一个确认框”。
这对单 agent CLI 来说还算够用;但对 Claude Code 这种支持:
- 主 agent
- background agent
- in-process teammate
- remote session
- MCP server
的系统来说,权限早就不是 UI 细节,而是runtime 的控制面。
这篇文章想讲清楚四件事:
- Claude Code 的权限决策链路到底怎么走
- 单 agent 和多 agent 的权限模型是怎么统一的
- 沙箱和权限不是一回事,它们分别解决什么问题
- 为什么这套设计对多 Agent 研究尤其有启发
一、Claude Code 的权限系统,核心不是“允许/拒绝”,而是把决策拆成多个阶段
如果从源码入口看,Claude Code 的权限不是散落在各个工具里临时判断,而是有一条比较清晰的控制链:
src/utils/permissions/permissions.tssrc/hooks/toolPermission/PermissionContext.tssrc/hooks/toolPermission/handlers/interactiveHandler.tssrc/hooks/toolPermission/handlers/coordinatorHandler.tssrc/hooks/toolPermission/handlers/swarmWorkerHandler.tssrc/tools/BashTool/bashPermissions.ts
这几层组合起来之后,Claude Code 的权限检查大致分成下面几步:
- 工具发起调用
- 静态规则判断
- Hook / classifier 等自动决策
- 如果仍然不确定,则进入 ask 路径
- 由不同 handler 决定 ask 应该怎么被处理
关键在于,它没有把权限简化成一个同步布尔值,而是把它建模成了一个 richer result:
allowdenyask
这看起来只是多一个状态,但其实影响非常大。
因为对于 agent runtime 来说,真正麻烦的从来不是“能不能执行”,而是:
当系统还不能安全地下结论时,应该把决策交给谁、通过什么通道交给谁、等待期间如何保持运行时一致性。
Claude Code 在这一点上是非常工程化的。
二、PermissionContext 是真正的中枢:它把规则、用户、classifier、hook 和日志都绑在一起
PermissionContext.ts 是这套系统最值得读的文件之一。
它做的事情,不只是“包装一下弹窗上下文”,而是把一次权限请求变成一个可以被 runtime 消化的对象。
它至少统一了这些能力:
- 记录 tool、input、assistantMessage、toolUseID
- 记录和上报 permission decision
- 把用户允许动作转成
PermissionUpdate - 持久化“always allow / always deny”这类规则
- 支持 hook 先行决策
- 支持 bash classifier 自动批准
- 支持 abort、reject、feedback、content block 回流
这意味着 Claude Code 的权限系统不是“UI 提示层”,而是一个有副作用编排能力的事务上下文。
这点很重要。
因为权限通过之后,并不是简单返回一个 true,而是可能要同时完成:
- 修改输入参数
- 写入新的 permission rule
- 更新 AppState 中的 permission context
- 记录审计日志
- 继续 tool execution
也就是说,Claude Code 把“批准一次工具调用”设计成了一个可追踪、可持久化、可回放的运行时事件。
三、真正的权限决策链路:先走确定性规则,再走自动化判断,最后才找人
很多工具会把用户确认放在最前面:不确定就弹窗。
Claude Code 更像一个真正的控制系统,它先尽量把决策自动化。
从源码抽象后,大致是这条链路:
第 1 层:显式规则
在 permissions.ts 和 permissionSetup.ts 中,Claude Code 会先加载不同来源的规则:
- policySettings
- projectSettings
- userSettings
- localSettings
- cliArg
- session
- command
这一步体现了很清晰的“规则来源层级”思维:
权限不是一份配置,而是多来源规则的合并结果。
这种设计的现实意义是:
- 企业策略可以高优先级覆盖
- 项目级规则可以表达仓库约束
- 用户级规则可以表达个人习惯
- session 级规则可以承载临时授权
很多 agent 产品做不到这一步,结果就是安全策略无法分层,只能“一刀切”。
第 2 层:工具级安全逻辑
不同工具还有自己的领域逻辑。
最典型的是 Bash。
bashPermissions.ts 不是简单用一个字符串前缀匹配,而是做了很多细粒度工作:
- shell command parsing
- subcommand prefix 提取
- compound command 拆分
- wildcard / prefix rule 匹配
- 语义级安全检查
- 高风险 wrapper / interpreter 检测
这说明 Claude Code 很明确地知道一件事:
Bash 权限不是“工具权限”,而是“代码执行权限”。
因此它不能只靠通用权限框架,还需要 Bash 自己的安全语义层。
第 3 层:Hook 与 classifier
如果规则层没有直接裁定,接下来 Claude Code 会尝试自动化决策:
- hook
- bash classifier
- 某些模式下的 yolo / transcript classifier 逻辑
这里最值得注意的是:Claude Code 把 classifier 当作自动化裁判,但并没有把它当作最终主宰。
它更像是在做:
- 规则优先
- 自动化推断补位
- 实在不确定才进入 ask
这是一种很成熟的工程姿态。
因为 classifier 再强,本质上也是概率系统;而权限系统要承担的是错误成本。
四、三种 ask handler,是 Claude Code 权限系统最精彩的地方
如果把 allow / deny / ask 看作协议,那真正决定系统气质的,是 ask 怎么处理。
Claude Code 并没有用一个统一弹窗解决所有情况,而是针对不同运行时准备了不同 handler。
1. interactiveHandler:主会话的交互式权限流
文件:
src/hooks/toolPermission/handlers/interactiveHandler.ts
这个 handler 是主 agent 的标准权限路径。
它做的事情比“弹框”复杂得多:
- 把请求压入 confirm queue
- 并行等待 hook / classifier / 用户交互 / bridge response / channel response
- 用
resolveOnce避免多个异步源重复 resolve - 用户开始交互后会终止 classifier 自动批准窗口
这一点很有意思:Claude Code 不是把用户和自动化串行排队,而是在做竞争式权限决策。
谁先得出可靠结果,谁赢。
从系统设计上说,这明显比“先等 classifier,classifier 不行再弹窗”更顺滑,也更节省时间。
2. coordinatorHandler:Coordinator worker 的半自动流
文件:
src/hooks/toolPermission/handlers/coordinatorHandler.ts
这里的逻辑更偏 orchestrator:
- 先跑 hook
- 再跑 classifier
- 都没结果再回落到 interactive dialog
它的关键不是复杂度,而是角色差异。
Claude Code 明显在区分:
- 主交互 agent
- coordinator worker
也就是说,不同 agent 角色即便共用同一套权限框架,进入 ask 状态之后的处理优先级仍然可以不同。
这非常值得学。
3. swarmWorkerHandler:多 Agent 协作下的权限转发
文件:
src/hooks/toolPermission/handlers/swarmWorkerHandler.tssrc/utils/swarm/permissionSync.tssrc/utils/swarm/leaderPermissionBridge.ts
这是我觉得最有启发的一部分。
在 swarm 模式下,一个 worker 如果要写文件、跑 Bash,它不应该各自弹一个权限框给用户。
Claude Code 的做法是:
- 如果 classifier 能自动批,就先批
- 否则把 permission request 发给 leader
- leader 统一决策
- worker 等待 leader 的响应再继续执行
这说明在 Claude Code 里,权限已经不是“用户和当前 agent 的关系”,而是:
leader 与 worker 的协调协议。
这件事非常关键。
因为一旦进入多 Agent 时代,权限系统如果还停留在“每个 agent 自己弹窗”,就会很快崩溃:
- 用户界面会爆炸
- 审批来源会混乱
- 责任链不清楚
- 无法做统一审计
Claude Code 通过 leader bridge + mailbox sync 解决了这个问题。
我认为这是它比很多“多 agent 框架 demo”成熟得多的根本原因之一。
五、单 agent 和多 agent 的统一,不是通过共用弹窗,而是通过共用 PermissionDecision 协议
表面上看,主 agent 和 swarm worker 的权限处理方式差很多:
- 一个走 UI queue
- 一个走 leader mailbox
但从系统内部看,它们其实统一在同一个抽象上:
PermissionDecisionPermissionUpdatePermissionContext
这意味着 Claude Code 并不是为每种场景重新发明权限系统,而是把差异控制在 handler 这一层。
这是一种非常好的分层:
统一的部分
- 规则来源
- allow / deny / ask 协议
- 更新和持久化规则的方法
- decision logging
- tool use context
变化的部分
- ask 交给谁
- 等待谁回复
- UI 怎么呈现
- 是否存在 bridge 或 mailbox
这给我们一个很重要的启发:
多 Agent 不是多套权限系统,而是一套权限协议 + 多种决策承载通道。
这个思路非常值得抄。
六、Bash classifier 真正扮演的,不是“安全模型”,而是降低人类审批负担的中间层
很多人一听 classifier,就会往“安全 AI 审核”方向理解。
但从 Claude Code 的实现看,它更像一个 practical middle layer:
- 能自动批准明显安全的 bash 行为
- 减少用户被频繁打断
- 对高风险命令保持 ask 或 deny
也就是说,classifier 在这里不是为了替代权限系统,而是为了让权限系统能在真实工作流中被接受。
这是一个很成熟的产品判断。
因为如果没有 classifier,终端 agent 在写代码、跑测试、读日志时会频繁触发确认;
而如果完全依赖 classifier,又会把高风险错误放大。
Claude Code 选的是中间路线:
- rule 是硬边界
- classifier 是减压阀
- human approval 是最终兜底
这比“全自动”或“全手动”都更现实。
七、沙箱不是权限系统的替代,而是权限系统的执行边界
这是本文最想强调的一点。
很多人容易把“权限”和“沙箱”混成一个概念。
Claude Code 明显把二者分开了。
相关文件:
src/utils/sandbox/sandbox-adapter.tssrc/tools/BashTool/shouldUseSandbox.tssrc/utils/permissions/filesystem.ts
它们大致承担的是两个不同问题:
权限系统回答的是:
- 这次 tool use 应不应该被允许
- 允许后要不要写入新的 permission rule
- 由谁批准这次操作
沙箱回答的是:
- 即使允许了,这次执行最多能碰到哪里
- 文件系统写入边界是什么
- 网络域名边界是什么
- 是否忽略某些违规
Claude Code 的 sandbox-adapter.ts 很能说明问题。
它不是一个孤立安全模块,而是把 Claude Code 现有设置与 permission rule 转换成 @anthropic-ai/sandbox-runtime 可执行的 runtime config。
也就是说,沙箱不是另起炉灶的世界,而是:
把上层规则投影成 OS 级运行限制。
这个分层非常正确。
如果没有权限层,沙箱会过于僵硬,用户体验很差;
如果没有沙箱层,权限通过之后工具仍然可能做得太多。
Claude Code 的做法是:
- 权限层负责“决策”
- 沙箱层负责“边界”
这是控制系统中非常经典、也非常必要的一刀。
八、filesystem 权限值得单独研究:Claude Code 明显在防止“配置文件成为逃逸点”
src/utils/permissions/filesystem.ts 非常值得单独看。
这里面有一条非常明确的安全思想:
Claude Code 不只是防止你改错业务代码,它更在防止 agent 修改会影响自身权限和执行环境的配置文件。
例如它会特别保护:
.claude/settings.json.claude/settings.local.json.claude/skills.gitconfig.gitmodules- shell profile
.mcp.json
这背后的逻辑很清楚:
真正危险的不是“改错了一个业务文件”,而是 agent 改写了自己未来的能力边界。
这一点和很多简单的 coding agent 权限设计完全不同。
很多系统只在乎“能不能写项目目录”,Claude Code 已经在考虑“能不能通过改配置间接扩大未来权限”。
这说明它对长期运行 agent 的风险模型是比较成熟的。
九、MCP 审批说明了 Claude Code 的权限面不只在本地工具
还有一个容易被忽略的点:
src/services/mcpServerApproval.tsx
这说明 Claude Code 的权限边界不仅发生在 Bash / Edit / Write 这种本地工具上,也发生在外部能力接入这一层。
具体来说,项目级 MCP server 并不是连上就算完了,而是有 pending approval 流程。
这说明 Claude Code 把“给 agent 接入一个新外部系统”视为一种高阶权限行为。
这个判断非常重要。
因为在 agent 时代,风险不只来自本地 shell,还来自:
- 新增可调用 API
- 新增外部资源访问面
- 新增远程执行能力
MCP 审批本质上是在控制 agent 的 capability surface。
这也是为什么我觉得 Claude Code 的权限系统不是工具层小模块,而是整个 runtime 的能力治理层。
十、对多 Agent 研究最有启发的三个点
最后,总结三条我觉得最值得研究者带走的结论。
1. 权限系统必须协议化
只要系统进入多 agent、异步执行、remote session,权限就不可能继续是“一个同步弹窗”。
它必须是协议:
- 谁发起
- 谁审批
- 谁等待
- 谁记录
- 决策如何持久化
Claude Code 在这点上已经走到了真正工程系统的层级。
2. 多 Agent 的统一,不靠统一 UI,而靠统一 decision model
主 agent、coordinator、swarm worker 的 ask 处理方式都不同,但都归一到同一个 PermissionDecision 协议。
这是一种很强的架构能力。
3. 沙箱和权限一定要分层
权限决定“是否放行”,沙箱决定“放行后最多能做什么”。
如果把这两件事揉在一起,系统会要么不安全,要么不可用。
Claude Code 这套分工非常值得借鉴。
结语:Claude Code 的权限系统,本质上是在给 Agent Runtime 建立宪法
上一篇写多 Agent 编排时,我说 Claude Code 更像在做 Agent OS。
这篇读完之后,我会更进一步:
Claude Code 的权限系统,像是在给这套 Agent OS 写宪法。
它规定:
- 哪些能力能默认放行
- 哪些能力需要领导层审批
- 哪些能力即使放行也必须被沙箱约束
- 哪些配置文件永远不能随便碰
- 多 Agent 之间如何转发权限请求
这已经不是普通 CLI 的思路了。
它说明真正成熟的 agent 系统竞争,最终一定会进入这些地方:
- control plane
- permission protocol
- capability governance
- sandbox boundary
- auditability
如果你也在做多 Agent,这一套值得认真研究。
下一篇我准备继续拆:
QueryEngine 与主循环:Claude Code 为什么不是聊天机器人,而是一个 runtime。
深求社区(DeepSeek.club)出品,国内领先的AI大模型及应用开源社区!