Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

utils/permissions/ 实现 Claude Code 的工具权限大脑:从 settings / policy / managed 源加载 allow·deny·ask 规则,调用各 Tool 的 checkPermissions,在 auto 模式走 transcript classifier,在 dontAsk 模式把 ask 折叠为 deny,并通过 hasPermissionsToUseTool 暴露为 CanUseToolFn 供 query 与 hooks/useCanUseTool 消费。本章以 permissions.ts 为主线,串联 loader、setup、classifier 与 PermissionRule 类型。

学完本章你应该能

  • 画出 hasPermissionsToUseTool 外层包装与 Inner 求值顺序
  • 说明 deny / ask 规则与工具自带 checkPermissions 的优先级
  • 理解 dontAsk 与 auto 模式对 ask 的不同变换
  • 能在 permissionSetup 中找到规则加载与模式切换入口
  • 区分 PermissionRequest hook(utils/hooks.ts)与规则引擎

核心概念(先读懂这些)

PermissionDecision 三分支

行为只有 allow、deny、ask。allow 可携带更新后的 input(例如自动修正路径)。deny 带 message 与 decisionReason(rule、hook、safetyCheck、mode 等)。ask 触发 UI 或 classifier。hasPermissionsToUseTool 在外层处理 auto 模式 consecutive denials 重置、dontAsk 变换、classifier 与 PowerShell 特殊策略,Inner 函数做规则与工具检查。

规则源与 allowManagedPermissionRulesOnly

permissionsLoader 从多 SettingSource 读取 JSON 规则;policySettings 可开启 allowManagedPermissionRulesOnly,此时仅 managed 规则生效,且 UI 隐藏「始终允许」类选项(shouldShowAlwaysAllowOptions)。企业部署与本地开发的行为差异往往来自这里,而非 permissions.ts 核心逻辑。

与 useCanUseTool 的分层

permissions.ts 不做 React、不入队弹窗。hooks/useCanUseTool 在 ask 时 push PermissionRequest。调试时先 log Inner 返回的 decisionReason,再看 UI 是否入队。PermissionRequest 事件 还可被 utils/hooks.ts 用户脚本拦截(headless agent 路径在 permissions.ts 前段也有 hook 尝试)。

建议学习步骤

  1. 阅读 hasPermissionsToUseTool 导出与 auto/dontAsk 包装
  2. 阅读 hasPermissionsToUseToolInner 步骤 1a–1d
  3. 阅读 PermissionRule 的 allow/deny/ask 语义
  4. 阅读 permissionSetup 的加载与模式迁移
  5. 阅读 permissionsLoader 的 managed-only 开关

常见误区

注意

bypassPermissions 与 acceptEdits 快路径在 Inner 更深处,勿与 1a deny 混淆

注意

classifierApprovable 的 safetyCheck 在 auto 模式仍可能保持 ask

注意

PowerShell 在 auto 模式有单独门控(POWERSHELL_AUTO_MODE feature)

目录结构与职责划分

utils/permissions/ 核心文件:

文件职责
permissions.tshasPermissionsToUseTool、规则匹配、classifier 集成
permissionsLoader.ts从磁盘/settings 加载规则
permissionSetup.ts启动时组装 ToolPermissionContext、模式切换
PermissionRule.ts / permissionRuleParser.ts规则 AST 与序列化
bashClassifier.ts / yoloClassifier.tsauto 模式分类器
denialTracking.ts连续拒绝计数
pathValidation.ts / dangerousPatterns.ts路径与安全模式

约 24 个 TS 文件协同,permissions.ts 单行数最多,是阅读入口。

hasPermissionsToUseTool 外层:模式变换与计数

导出函数(约 473 行)先 await Inner,再:

  1. allow 且 auto 模式: 若 consecutiveDenials > 0,recordSuccess 并 persistDenialState(TRANSCRIPT_CLASSIFIER feature)
  2. ask + dontAsk 模式: 转为 deny,message 用 DONT_ASK_REJECT_MESSAGE(来自 messages.ts)
  3. ask + auto/plan+auto: 走 classifier 分支;非 classifierApprovable 的 safetyCheck 在 shouldAvoidPermissionPrompts 时直接 deny
  4. requiresUserInteraction 工具: 保持 ask,不走 classifier 自动批准

这段包装保证「模式开关」无法被 Inner 的早期 allow 绕过(注释:dontAsk 在末尾转换以免被 bypass)。

阅读时对照 ToolPermissionContext.mode 枚举(PermissionMode.ts)。

源码引用: src/utils/permissions/permissions.ts · 第 473–560 行(共 1487 行)

 473| export const hasPermissionsToUseTool: CanUseToolFn = async (
 474|   tool,
 475|   input,
 476|   context,
 477|   assistantMessage,
 478|   toolUseID,
 479| ): Promise<PermissionDecision> => {
 480|   const result = await hasPermissionsToUseToolInner(tool, input, context)
 481| 
 482| 
 483|   // Reset consecutive denials on any allowed tool use in auto mode.
 484|   // This ensures that a successful tool use (even one auto-allowed by rules)
 485|   // breaks the consecutive denial streak.
 486|   if (result.behavior === 'allow') {
 487|     const appState = context.getAppState()
 488|     if (feature('TRANSCRIPT_CLASSIFIER')) {
 489|       const currentDenialState =
 490|         context.localDenialTracking ?? appState.denialTracking
 491|       if (
 492|         appState.toolPermissionContext.mode === 'auto' &&
 493|         currentDenialState &&
 494|         currentDenialState.consecutiveDenials > 0
 495|       ) {
 496|         const newDenialState = recordSuccess(currentDenialState)
 497|         persistDenialState(context, newDenialState)
 498|       }
 499|     }
 500|     return result
 501|   }
 502| 
 503|   // Apply dontAsk mode transformation: convert 'ask' to 'deny'
 504|   // This is done at the end so it can't be bypassed by early returns
 505|   if (result.behavior === 'ask') {
 506|     const appState = context.getAppState()
 507| 
 508|     if (appState.toolPermissionContext.mode === 'dontAsk') {
 509|       return {
 510|         behavior: 'deny',
 511|         decisionReason: {
 512|           type: 'mode',
 513|           mode: 'dontAsk',
 514|         },
 515|         message: DONT_ASK_REJECT_MESSAGE(tool.name),
 516|       }
 517|     }
 518|     // Apply auto mode: use AI classifier instead of prompting user
 519|     // Check this BEFORE shouldAvoidPermissionPrompts so classifiers work in headless mode
 520|     if (
 521|       feature('TRANSCRIPT_CLASSIFIER') &&
 522|       (appState.toolPermissionContext.mode === 'auto' ||
 523|         (appState.toolPermissionContext.mode === 'plan' &&
 524|           (autoModeStateModule?.isAutoModeActive() ?? false)))
 525|     ) {
 526|       // Non-classifier-approvable safetyCheck decisions stay immune to ALL
 527|       // auto-approve paths: the acceptEdits fast-path, the safe-tool allowlist,
 528|       // and the classifier. Step 1g only guards bypassPermissions; this guards
 529|       // auto. classifierApprovable safetyChecks (sensitive-file paths) fall
 530|       // through to the classifier — the fast-paths below naturally don't fire
 531|       // because the tool's own checkPermissions still returns 'ask'.
 532|       if (
 533|         result.decisionReason?.type === 'safetyCheck' &&
 534|         !result.decisionReason.classifierApprovable
 535|       ) {
 536|         if (appState.toolPermissionContext.shouldAvoidPermissionPrompts) {
 537|           return {
 538|             behavior: 'deny',
 539|             message: result.message,
 540|             decisionReason: {
 541|               type: 'asyncAgent',
 542|               reason:
 543|                 'Safety check requires interactive approval and permission prompts are not available in this context',
 544|             },
 545|           }
 546|         }
 547|         return result
 548|       }
 549|       if (tool.requiresUserInteraction?.() && result.behavior === 'ask') {
 550|         return result
 551|       }
 552| 
 553|       // Use local denial tracking for async subagents (whose setAppState
 554|       // is a no-op), otherwise read from appState as before.
 555|       const denialState =
 556|         context.localDenialTracking ??
 557|         appState.denialTracking ??
 558|         createDenialTrackingState()
 559| 
 560|       // PowerShell requires explicit user permission in auto mode unless

hasPermissionsToUseToolInner:规则与工具检查

Inner(约 1158 行起)按序:

1. 规则层

  • 1a denyRule:整工具拒绝
  • 1b askRule:整工具强制询问;沙箱 autoAllowBash 可跳过 Bash ask
  • 1c 调用 tool.inputSchema.parse + tool.checkPermissions
  • 1d 工具返回 deny 则立即 deny

2. 后续步骤(本段后继续读文件) 包括 allow 规则、acceptEdits、bypassPermissions killswitch、shadowed rule 检测等。

abort 时抛 AbortError,不返回 deny。无效 schema 记 logError 并可能 passthrough。

实战: 若 Bash 总是 ask,先查 askRule 与 checkPermissions,再看 sandbox 配置。

源码引用: src/utils/permissions/permissions.ts · 第 1158–1228 行(共 1487 行)

1158| async function hasPermissionsToUseToolInner(
1159|   tool: Tool,
1160|   input: { [key: string]: unknown },
1161|   context: ToolUseContext,
1162| ): Promise<PermissionDecision> {
1163|   if (context.abortController.signal.aborted) {
1164|     throw new AbortError()
1165|   }
1166| 
1167|   let appState = context.getAppState()
1168| 
1169|   // 1. Check if the tool is denied
1170|   // 1a. Entire tool is denied
1171|   const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
1172|   if (denyRule) {
1173|     return {
1174|       behavior: 'deny',
1175|       decisionReason: {
1176|         type: 'rule',
1177|         rule: denyRule,
1178|       },
1179|       message: `Permission to use ${tool.name} has been denied.`,
1180|     }
1181|   }
1182| 
1183|   // 1b. Check if the entire tool should always ask for permission
1184|   const askRule = getAskRuleForTool(appState.toolPermissionContext, tool)
1185|   if (askRule) {
1186|     // When autoAllowBashIfSandboxed is on, sandboxed commands skip the ask rule and
1187|     // auto-allow via Bash's checkPermissions. Commands that won't be sandboxed (excluded
1188|     // commands, dangerouslyDisableSandbox) still need to respect the ask rule.
1189|     const canSandboxAutoAllow =
1190|       tool.name === BASH_TOOL_NAME &&
1191|       SandboxManager.isSandboxingEnabled() &&
1192|       SandboxManager.isAutoAllowBashIfSandboxedEnabled() &&
1193|       shouldUseSandbox(input)
1194| 
1195|     if (!canSandboxAutoAllow) {
1196|       return {
1197|         behavior: 'ask',
1198|         decisionReason: {
1199|           type: 'rule',
1200|           rule: askRule,
1201|         },
1202|         message: createPermissionRequestMessage(tool.name),
1203|       }
1204|     }
1205|     // Fall through to let Bash's checkPermissions handle command-specific rules
1206|   }
1207| 
1208|   // 1c. Ask the tool implementation for a permission result
1209|   // Overridden unless tool input schema is not valid
1210|   let toolPermissionResult: PermissionResult = {
1211|     behavior: 'passthrough',
1212|     message: createPermissionRequestMessage(tool.name),
1213|   }
1214|   try {
1215|     const parsedInput = tool.inputSchema.parse(input)
1216|     toolPermissionResult = await tool.checkPermissions(parsedInput, context)
1217|   } catch (e) {
1218|     // Rethrow abort errors so they propagate properly
1219|     if (e instanceof AbortError || e instanceof APIUserAbortError) {
1220|       throw e
1221|     }
1222|     logError(e)
1223|   }
1224| 
1225|   // 1d. Tool implementation denied permission
1226|   if (toolPermissionResult?.behavior === 'deny') {
1227|     return toolPermissionResult
1228|   }

PermissionRule:allow / deny / ask

PermissionRule 由 toolName + ruleContent 组成,behavior 为 allow | deny | ask。

  • allow: 匹配则倾向放行(仍可能被子sequent safety 拦截)
  • deny: 硬拒绝,message 通常固定
  • ask: 强制弹窗;Bash 规则可带命令模式,由 shellRuleMatching 解析

permissionRuleValueSchema 用 zod lazySchema,类型定义已抽到 types/permissions.ts 打破循环依赖。

用户可见的 /permissions 命令修改规则后,通过 PermissionUpdate 应用,permissionSetup 负责刷新 context。

源码引用: src/utils/permissions/PermissionRule.ts · 第 19–40 行(共 41 行)

  19| /**
  20|  * ToolPermissionBehavior is the behavior associated with a permission rule.
  21|  * 'allow' means the rule allows the tool to run.
  22|  * 'deny' means the rule denies the tool from running.
  23|  * 'ask' means the rule forces a prompt to be shown to the user.
  24|  */
  25| export const permissionBehaviorSchema = lazySchema(() =>
  26|   z.enum(['allow', 'deny', 'ask']),
  27| )
  28| 
  29| /**
  30|  * PermissionRuleValue is the content of a permission rule.
  31|  * @param toolName - The name of the tool this rule applies to
  32|  * @param ruleContent - The optional content of the rule.
  33|  *   Each tool may implement custom handling in `checkPermissions()`
  34|  */
  35| export const permissionRuleValueSchema = lazySchema(() =>
  36|   z.object({
  37|     toolName: z.string(),
  38|     ruleContent: z.string().optional(),
  39|   }),
  40| )

permissionSetup:启动与模式迁移

permissionSetup.ts 连接 bootstrap state、settings、growthbook feature gate:

  • loadAllPermissionRulesFromDisk 合并各源规则
  • applyPermissionRulesToPermissionContext 写入 ToolPermissionContext
  • handleAutoModeTransition / handlePlanModeTransition 与 plan 模式、auto 互斥逻辑
  • 危险模式 DANGEROUS_BASH_PATTERNS、CROSS_PLATFORM_CODE_EXEC 用于规则建议与安全提示
  • isOverlyBroadPowerShellAllowRule 等防止 PowerShell(*) 过宽 allow

阅读建议: 搜索 applyPermissionUpdate 看 UI 确认后如何持久化到 settings 文件。

源码引用: src/utils/permissions/permissionSetup.ts · 第 1–80 行(共 1533 行)

   1| import { feature } from 'bun:bundle'
   2| import { relative } from 'path'
   3| import {
   4|   getOriginalCwd,
   5|   handleAutoModeTransition,
   6|   handlePlanModeTransition,
   7|   setHasExitedPlanMode,
   8|   setNeedsAutoModeExitAttachment,
   9| } from '../../bootstrap/state.js'
  10| import type {
  11|   ToolPermissionContext,
  12|   ToolPermissionRulesBySource,
  13| } from '../../Tool.js'
  14| import { getCwd } from '../cwd.js'
  15| import { isEnvTruthy } from '../envUtils.js'
  16| import type { SettingSource } from '../settings/constants.js'
  17| import { SETTING_SOURCES } from '../settings/constants.js'
  18| import {
  19|   getSettings_DEPRECATED,
  20|   getSettingsFilePathForSource,
  21|   getUseAutoModeDuringPlan,
  22|   hasAutoModeOptIn,
  23| } from '../settings/settings.js'
  24| import {
  25|   type PermissionMode,
  26|   permissionModeFromString,
  27| } from './PermissionMode.js'
  28| import { applyPermissionRulesToPermissionContext } from './permissions.js'
  29| import { loadAllPermissionRulesFromDisk } from './permissionsLoader.js'
  30| 
  31| /* eslint-disable @typescript-eslint/no-require-imports */
  32| const autoModeStateModule = feature('TRANSCRIPT_CLASSIFIER')
  33|   ? (require('./autoModeState.js') as typeof import('./autoModeState.js'))
  34|   : null
  35| 
  36| import { resolve } from 'path'
  37| import {
  38|   checkSecurityRestrictionGate,
  39|   checkStatsigFeatureGate_CACHED_MAY_BE_STALE,
  40|   getDynamicConfig_BLOCKS_ON_INIT,
  41|   getFeatureValue_CACHED_MAY_BE_STALE,
  42| } from 'src/services/analytics/growthbook.js'
  43| import {
  44|   addDirHelpMessage,
  45|   validateDirectoryForWorkspace,
  46| } from '../../commands/add-dir/validation.js'
  47| import {
  48|   type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  49|   logEvent,
  50| } from '../../services/analytics/index.js'
  51| import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'
  52| import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js'
  53| /* eslint-enable @typescript-eslint/no-require-imports */
  54| import { POWERSHELL_TOOL_NAME } from '../../tools/PowerShellTool/toolName.js'
  55| import { getToolsForDefaultPreset, parseToolPreset } from '../../tools.js'
  56| import {
  57|   getFsImplementation,
  58|   safeResolvePath,
  59| } from '../../utils/fsOperations.js'
  60| import { modelSupportsAutoMode } from '../betas.js'
  61| import { logForDebugging } from '../debug.js'
  62| import { gracefulShutdown } from '../gracefulShutdown.js'
  63| import { getMainLoopModel } from '../model/model.js'
  64| import {
  65|   CROSS_PLATFORM_CODE_EXEC,
  66|   DANGEROUS_BASH_PATTERNS,
  67| } from './dangerousPatterns.js'
  68| import type {
  69|   PermissionRule,
  70|   PermissionRuleSource,
  71|   PermissionRuleValue,
  72| } from './PermissionRule.js'
  73| import {
  74|   type AdditionalWorkingDirectory,
  75|   applyPermissionUpdate,
  76| } from './PermissionUpdate.js'
  77| import type { PermissionUpdateDestination } from './PermissionUpdateSchema.js'
  78| import {
  79|   normalizeLegacyToolName,
  80|   permissionRuleValueFromString,

源码引用: src/utils/permissions/permissionSetup.ts · 第 64–76 行(共 1533 行)

  64| import {
  65|   CROSS_PLATFORM_CODE_EXEC,
  66|   DANGEROUS_BASH_PATTERNS,
  67| } from './dangerousPatterns.js'
  68| import type {
  69|   PermissionRule,
  70|   PermissionRuleSource,
  71|   PermissionRuleValue,
  72| } from './PermissionRule.js'
  73| import {
  74|   type AdditionalWorkingDirectory,
  75|   applyPermissionUpdate,
  76| } from './PermissionUpdate.js'

permissionsLoader:磁盘与 managed-only

shouldAllowManagedPermissionRulesOnly 读 policySettings.allowManagedPermissionRulesOnly。

shouldShowAlwaysAllowOptions 取反,控制 PermissionRequest UI 是否展示「始终允许」。

SUPPORTED_RULE_BEHAVIORS 限定 allow/deny/ask。load 流程解析字符串规则(permissionRuleValueFromString)并标注 SettingSource,供 shadowedRuleDetection 提示用户哪条规则被覆盖。

企业策略文件变更应触发 ConfigChange hook(见 shell-hooks 章)做审计。

源码引用: src/utils/permissions/permissionsLoader.ts · 第 27–50 行(共 297 行)

  27| /**
  28|  * Returns true if allowManagedPermissionRulesOnly is enabled in managed settings (policySettings).
  29|  * When enabled, only permission rules from managed settings are respected.
  30|  */
  31| export function shouldAllowManagedPermissionRulesOnly(): boolean {
  32|   return (
  33|     getSettingsForSource('policySettings')?.allowManagedPermissionRulesOnly ===
  34|     true
  35|   )
  36| }
  37| 
  38| /**
  39|  * Returns true if "always allow" options should be shown in permission prompts.
  40|  * When allowManagedPermissionRulesOnly is enabled, these options are hidden.
  41|  */
  42| export function shouldShowAlwaysAllowOptions(): boolean {
  43|   return !shouldAllowManagedPermissionRulesOnly()
  44| }
  45| 
  46| const SUPPORTED_RULE_BEHAVIORS = [
  47|   'allow',
  48|   'deny',
  49|   'ask',
  50| ] as const satisfies PermissionBehavior[]

Headless 与 PermissionRequest hook

permissions.ts 前部(约 450 行)在 headless agent 路径尝试 PermissionRequest hook:hook 可返回 allow/deny 附加 message;失败则 logError 并 fall through。

这与 utils/hooks.ts 的 executePermissionRequestHooks 是同一事件名的不同调用上下文。REPL 内由 useCanUseTool → interactiveHandler 触发 hook;无 UI 时由引擎直接 ask hook。

调试: 若 CI agent 无故 deny,搜 PermissionRequest hook 配置与 shouldAvoidPermissionPrompts。

源码引用: src/utils/permissions/permissions.ts · 第 420–471 行(共 1487 行)

 420|       }
 421|       const decision = hookResult.permissionRequestResult
 422|       if (decision.behavior === 'allow') {
 423|         const finalInput = decision.updatedInput ?? input
 424|         // Persist permission updates if provided
 425|         if (decision.updatedPermissions?.length) {
 426|           persistPermissionUpdates(decision.updatedPermissions)
 427|           context.setAppState(prev => ({
 428|             ...prev,
 429|             toolPermissionContext: applyPermissionUpdates(
 430|               prev.toolPermissionContext,
 431|               decision.updatedPermissions!,
 432|             ),
 433|           }))
 434|         }
 435|         return {
 436|           behavior: 'allow',
 437|           updatedInput: finalInput,
 438|           decisionReason: {
 439|             type: 'hook',
 440|             hookName: 'PermissionRequest',
 441|           },
 442|         }
 443|       }
 444|       if (decision.behavior === 'deny') {
 445|         if (decision.interrupt) {
 446|           logForDebugging(
 447|             `Hook interrupt: tool=${tool.name} hookMessage=${decision.message}`,
 448|           )
 449|           context.abortController.abort()
 450|         }
 451|         return {
 452|           behavior: 'deny',
 453|           message: decision.message || 'Permission denied by hook',
 454|           decisionReason: {
 455|             type: 'hook',
 456|             hookName: 'PermissionRequest',
 457|             reason: decision.message,
 458|           },
 459|         }
 460|       }
 461|     }
 462|   } catch (error) {
 463|     // If hooks fail, fall through to auto-deny rather than crashing
 464|     logError(
 465|       new Error('PermissionRequest hook failed for headless agent', {
 466|         cause: toError(error),
 467|       }),
 468|     )
 469|   }
 470|   return null
 471| }

源码目录

点击 permissions/ 展开 classifier、denialTracking、filesystem 等子文件。yolo-classifier-prompts/ 下 txt 为分类器系统提示词,改文案需同步 classifier 版本测试。

动手练习

  1. 在 settings 添加 Bash(deny) 规则,触发 Bash tool_use,确认 decisionReason.type === 'rule'
  2. 切换 dontAsk,观察 ask 是否变为 deny 且 message 含 don't ask mode
  3. 在 hasPermissionsToUseTool 外层打日志,区分 Inner 与 classifier 改写
  4. 阅读 hooks 章 useCanUseTool,画一张从 hasPermissionsToUseTool 到弹窗的序列图

本章小结与延伸

permissions/ = 策略求值。UI 接缝在 hooks/useCanUseTool;扩展点在 settings 规则与 PermissionRequest hook。 继续学习:

  • useCanUseTool(hooks)
  • shell-hooks
Prev
session-storage · JSONL 会话持久化
Next
shell-hooks · 用户 Shell Hook 系统