Allow/Ask/Deny
三种权限行为
Section titled “三种权限行为”每一次工具调用,系统都会做出三种裁决之一:
| 行为 | 含义 | 返回类型 | 典型场景 |
|---|---|---|---|
| Allow | 自动放行,用户无感知 | { behavior: 'allow', updatedInput, decisionReason } | Read 读取项目内文件 |
| Ask | 弹出确认对话框 | { behavior: 'ask', message, suggestions, metadata } | Bash 执行未知命令 |
| Deny | 直接拒绝 | { behavior: 'deny', message, decisionReason } | 尝试执行被禁止的命令 |
这些行为由 PermissionResult 类型定义(src/utils/permissions/PermissionResult.ts)。
权限规则的五层来源
Section titled “权限规则的五层来源”规则从 5 个来源汇聚(PERMISSION_RULE_SOURCES,permissions.ts:109),优先级从高到低:
1. session — 用户在当前对话中手动授权("Always allow")2. cliArg — 命令行 --allow/--deny 参数3. command — Skill 工具的 allowedTools 白名单4. projectSettings — .claude/settings.json(团队共享)5. userSettings — ~/.claude/settings.json(跨项目)6. policySettings — 企业管理员下发的策略(用户不可覆盖)每个来源维护三个数组:alwaysAllowRules[source]、alwaysAskRules[source]、alwaysDenyRules[source]。
规则数据结构为 PermissionRule:
{ source: PermissionRuleSource // 来自哪个层级 ruleBehavior: 'allow' | 'ask' | 'deny' ruleValue: { toolName: string // 如 "Bash"、"mcp__server1" ruleContent?: string // 如 "git *"、"src/**" }}规则匹配引擎
Section titled “规则匹配引擎”permissions.ts 实现了三种匹配维度:
1. 工具名匹配(toolMatchesRule(),第 238 行)
匹配整个工具,仅当规则没有 ruleContent:
// 精确匹配rule "Bash" → 匹配 BashToolrule "mcp__server1" → 匹配该 MCP Server 的所有工具(server 级别)rule "mcp__server1__*" → 通配符匹配(同上)MCP 工具使用 getToolNameForPermissionCheck() 获取匹配名称,支持有前缀(mcp__server__tool)和无前缀模式。
2. 命令模式匹配(BashTool 的 checkPermissions())
BashTool 通过 preparePermissionMatcher()(Tool.ts:514)解析命令模式:
{"tool": "Bash", "ruleContent": "git *"} → 匹配 "git commit -m 'fix'"命令通过 AST 解析(readOnlyValidation.ts 使用 tree-sitter bash),提取第一个子命令进行匹配。
3. 路径匹配(文件工具的 checkPermissions())
Read/Edit/Write 工具通过 getPath() 提取文件路径,与 ruleContent 中的 glob 模式匹配:
{"tool": "Edit", "ruleContent": "src/**"} → 匹配 "src/utils/foo.ts"权限检查的完整流程
Section titled “权限检查的完整流程”每次工具调用的权限检查(canUseTool() → checkPermissions())经过以下步骤:
1a. Blanket deny 检查 getDenyRuleForTool() → 工具名完全匹配 deny 规则? ↓ 命中 → deny(工具在 getTools() 阶段就被过滤掉)
1b. Blanket allow 检查 toolAlwaysAllowedRule() → 工具名完全匹配 allow 规则? ↓ 命中 → allow
2. 工具自身 checkPermissions() 各工具有自定义逻辑: - BashTool: readOnlyValidation → sandbox 判定 → AST 解析 → 模式匹配 - FileEditTool: 路径白名单检查 - SkillTool: safe properties 白名单 + 精确/前缀匹配 ↓ 返回 PermissionResult
3. Hook 系统 executePermissionRequestHooks() → PreToolUse hook 可以 override ↓ hook 返回 deny → deny ↓ hook 返回 ask → 升级为 ask
4. Ask 规则检查 getAskRules() → 命中 → ask
5. 默认行为 根据当前 permissionMode 决定默认行为 - 'default': 大部分工具 ask - 'plan': 写操作 deny,读操作 allow - 'bypass': 全部 allow| 模式 | PermissionMode 值 | 适用场景 | 行为 |
|---|---|---|---|
| Default | 'default' | 日常使用 | 敏感操作逐一确认 |
| Plan Mode | 'plan' | 探索阶段 | 只能读不能写(isReadOnly() 检查) |
| Auto | 'auto' | 信任 AI | 通过 transcript classifier 自动决策 |
| Bypass | 'bypassPermissions' | 完全信任 | 所有操作自动放行(需显式 --dangerously-skip-permissions) |
Plan Mode 切换由 EnterPlanModeTool.call() 触发:
// EnterPlanModeTool.ts:88context.setAppState(prev => ({ ...prev, toolPermissionContext: applyPermissionUpdate( prepareContextForPlanMode(prev.toolPermissionContext), { type: 'setMode', mode: 'plan', destination: 'session' }, ),}))退出时由 ExitPlanModeV2Tool 恢复为之前的模式。
Denial Tracking:死循环防护
Section titled “Denial Tracking:死循环防护”src/utils/permissions/denialTracking.ts 实现了拒绝追踪机制:
const DENIAL_LIMITS = { maxDenialsPerTool: 3, // 同一工具连续拒绝上限 cooldownPeriodMs: 30_000, // 冷却期 30 秒}当 AI 被连续拒绝同一类操作达到上限时:
recordDenial()记录拒绝,增加计数shouldFallbackToPrompting()检测到连续拒绝,返回 true- 系统向 AI 注入消息:“Your previous tool call was rejected…”
- AI 被迫改变策略,避免”反复请求同一个被拒操作”的死循环
操作成功时调用 recordSuccess() 重置计数。
规则的运行时更新
Section titled “规则的运行时更新”权限规则可以在运行时动态更新(applyPermissionUpdate(),PermissionUpdate.ts):
type PermissionUpdate = | { type: 'addRule', behavior, rule, destination } | { type: 'removeRule', behavior, rule, destination } | { type: 'setMode', mode, destination }当用户在 Ask 对话框中选择 “Always allow”,系统调用 persistPermissionUpdates() 将规则写入对应层级的 settings 文件(project/user/managed),同时更新内存中的 toolPermissionContext。