Claude Code CLI 2.1.235 Auto Mode:权限系统里的两阶段 AI 分类器¶
Auto Mode 不是“模型想做什么就自动批准”,也不是一套取代权限规则的万能风控。它位于 Claude Code 本地权限系统中:确定性的 deny、ask、交互要求、组织上限和 safety floor 先裁决;只有仍需判断的动作才可能进入一个客户端驱动的两阶段分类器。分类器拿到经过整理的对话、工具动作、可信配置规则和少量环境事实,返回 XML verdict;客户端再把 verdict 映射为允许、策略阻断或 fail-closed。
本文只解释 2.1.235 发布 bundle 中可达的客户端实现。模型为什么给出某个判断、服务端 safeguard 如何工作、远端配置在某个账号上的实时取值,以及真实世界识别质量,均属于本文明确标出的 Boundary。
60 秒理解 Auto Mode¶
读者问题: 为什么开启 Auto Mode 后,有些 Bash 不问就执行,有些动作仍弹审批,有些又显示“分类器不可用”并拒绝?
一句话模型: Auto Mode 是权限管线中的一个 AI verdict 层;确定性规则先挡住不能交给分类器的动作,本地快速路径再放行明显安全的动作,剩余请求才进入 Stage 1/Stage 2,任何没有形成有效 verdict 的普通工具默认 fail closed。
贯穿场景:模型准备执行 Bash("git push origin main")。
- 客户端先查 deny/ask/managed policy、工具交互要求、组织审批上限和当前 permission mode;其中任一层要求阻止或询问,就不会让分类器越权覆盖。
- 如果动作在
acceptEdits模式模拟下本来就会允许,或命中客户端 safe allowlist,可跳过分类器。 - 否则客户端把历史 transcript、最新 Bash 调用、用户规则、repo visibility/git status 等事实送入 Auto Mode classifier。
- Stage 1 可以直接给出 allow;默认
both模式下,Stage 1 的 block 倾向还要进入 Stage 2 深判。 - Stage 2 明确 block 才是策略拒绝。API 不可用、XML 解析失败和 safeguard refusal 都会阻止普通工具,但这类 fail-closed 不等于分类器判断该命令危险。
- 拒绝后可以运行
PermissionDeniedHook。Hook 的retry:true只给下一轮模型追加“可以重试提议”的提示,原始工具调用仍未执行。
| 状态 | 谁拥有 | 成功时 | 失败时 | 是否已执行工具 |
|---|---|---|---|---|
| 确定性权限前置层 | 本地 permission/policy engine | 允许继续、直接允许或进入询问 | deny/ask/floor 终止 Auto Mode 路径 | 否 |
| 快速路径 | 本地工具规则 | acceptEdits 模拟或 safe allowlist 直接放行 |
未命中才构造 classifier 请求 | 否 |
| classifier 输入 | 本地 transcript/rule builder | 得到 system prompt、历史动作和当前 action | 上下文超限进入专用 fallback | 否 |
| Stage 1/2 | 模型请求 + 本地 XML parser | shouldBlock=false/true |
unavailable、parse failure、refusal 都无可靠 verdict | 否 |
| PermissionDenied Hook | 本地 hook runner | 可给模型追加 retry 建议 | Hook 错误不改变原 denial | 否 |
tool.call |
工具实现 | 真正产生文件、进程或远端副作用 | 只有前面最终 allow 才能到达 | 是 |
1. Auto Mode 在权限管线的准确位置¶
基础 permission engine 先执行确定性判断,顺序可从 395300-395335 恢复:
deny rule
-> tool.checkPermissions / tool 自身 safety check
-> ask rule
-> requiresUserInteraction
-> 组织级 ask ceiling / safety floor
-> bypassPermissions 或 plan-bypass
-> allow rule
-> 普通 ask
Auto Mode 的外层入口位于 395392-395501。它处理有效模式为 auto 的请求;配置允许时,plan 也可以采用 Auto Mode 语义,但 plan-mode floor 仍能把动作退回 ask。这里最重要的不是“分类器有多聪明”,而是 哪些裁决根本不交给它:
- safety check 若标记为不可由 classifier 批准,直接回退 ask/deny;
- 显式 ask rule、MCP server policy、组织
effectiveMaxPermission=ask保留审批上限; requiresUserInteraction工具通常回到真实对话框;- managed policy、deny rule、sandbox 和工具自身校验不会被 Auto Mode 替代;
- 排队等待 classifier 后,客户端重新读取 permission mode 和 Auto Mode active 状态。期间模式发生变化时,旧 verdict 不会直接套到新模式。
因此,Auto Mode 的权限关系不是“AI classifier > 所有规则”,而是:
两条本地快速路径¶
分类器调用有 token、延迟和可用性成本,所以 2.1.235 先做两类本地 fast path 395429-395452:
acceptEdits模拟。 客户端用经过过滤的 allow rule 和 permission layer 重新调用工具权限检查;如果该动作在acceptEdits下会直接允许,就以decisionReason.mode=auto放行。working-directory 场景还可以把已链接 worktree 作为临时允许范围再次计算。- safe allowlist。 对本版明确列入安全快速路径的工具/输入直接允许,并记录
fastPath=allowlist。
快速路径只是省掉 classifier 请求,不会越过此前已经命中的 deny、ask、组织上限或交互要求。
2. 配置不是简单对象覆盖¶
可配置字段¶
Auto Mode schema 暴露五个核心字段 39898:
| 字段 | 进入 prompt 的位置 | 语义 |
|---|---|---|
environment[] |
环境说明 | 告诉 classifier 当前项目、组织、基础设施和常用工作方式 |
allow[] |
allow section | 对默认 block 规则做窄范围 carve-out,仍受用户显式边界约束 |
soft_deny[] |
soft block section | 默认阻断破坏性/不可逆动作;足够具体的用户授权可以清除 |
hard_deny[] |
hard block section | 安全边界;用户意图不能清除 |
classifyAllShell |
permission fast-path gate | Auto Mode 活跃时暂停 Bash/PowerShell allow rule,让 shell 动作都经过 classifier |
skipAutoPermissionPrompt 和 useAutoModeDuringPlan 是 Auto Mode 周边开关,不属于四段 classifier prompt 内容。
只有三个可信来源可以写 classifier 规则¶
规则聚合器只接受 userSettings、flagSettings、policySettings。projectSettings 和 localSettings 由仓库内容影响,若其中出现合法 autoMode block,客户端只告警并忽略 42983-43007。这是一个明确的 prompt-injection 边界:项目不能通过提交 settings 自行放宽分类器。
三个可信来源的数组按来源顺序 追加,不是高优先级对象把低优先级整段覆盖。classifyAllShell 则独立计算:任一可信来源为 true 即生效。
$defaults 是有位置的 splice 指令¶
allow、soft_deny、hard_deny 和 environment 使用同一个展开器 277417-277427:
- 自定义数组不存在或为空:完整使用 shipped defaults;
- 第一次遇到字面量
$defaults:在该位置插入默认项; - 后续重复
$defaults:忽略; - 没有
$defaults:该数组完全替换默认段,而不是自动追加。
这使规则顺序可控,例如:
prompt builder 会把四个命名 slot 和 <settings_deny_rules> 替换为最终文本 277437-277462。普通 permissions.deny 还会被转成一段防绕过指令:即使模型换用 Bash、python -c、sed -i 或 heredoc 实现相同效果,也应阻断 277433-277435。
classifyAllShell=true 不会删除用户的 shell allow rule。它只在 Auto Mode 活跃时暂停这些 allow fast path;离开 Auto Mode 后原规则仍生效 328236-328249。
3. 分类器究竟看到了什么¶
默认 system prompt 不是一句“判断是否危险”,而是一份长安全合同 325417-325749:
- threat model:prompt injection、scope creep、accidental damage;
- 默认原则:不匹配 hard/soft block 的动作允许;
- hard block 与 soft block 的差异;
- 用户授权的高证据门槛和用户边界的低证据门槛;
- 对 assistant proposal、用户简短
yes、第三方消息插入、引用文本的解释规则; - push、发布、凭据、共享资源、生产变更、破坏性 Git 等细分类别;
- 明确排除低质量代码、浪费 token、错误答案等非权限风险。
所以 classifier 判断的对象是“这一次工具动作是否越过安全/授权边界”,不是评价 Agent 工作质量,也不是 Anthropic 账户级 abuse scoring。
transcript 不是原始 messages 直接拼接¶
输入构造器位于 326273-326310。它会:
- 从可见历史生成 classifier transcript,并按 role 统计用户、assistant 和 tool action 长度;
- 清理或格式化工具 input,保留当前待判断的 tool call 为最后 action;
- 对特定 git/gh 外发动作补充 repo visibility;
- 对可能破坏未提交内容的动作补充 live
git status; - 给 transcript 尾段和当前 action 设置 prompt-cache breakpoint;
- 估算
classifierChars / 4作为 token 诊断,但真实上下文限制仍由模型/API 返回决定。
历史工具 outcome 也有严格语义 325434-325441:ok 只代表 harness 没报告执行错误,不代表动作安全;interrupted 表示可能部分执行;automode-unavailable 与 automode-parsing-error 代表 fail-closed 而非策略判断;后台任务的 ok 只证明启动成功。
4. 两阶段 XML classifier¶
默认远端配置缺省值是 twoStageClassifier="both";远端也可以选择 fast 或 thinking 326424-326430。默认 both 的执行方式如下。
Stage 1:低输出预算的快速筛选¶
Stage 1 请求使用 querySource="auto_mode",默认输出上限为 64 + thinking overhead;若配置为 fast-only,则为 256 + thinking overhead。阶段外层超时为 60 秒,请求 SDK timeout 也是 60 秒 277499、326151-326169。
XML parser 接受 <block>yes|no</block>;启用 severity 模式时读取唯一 <severity> 数字。冲突的多个 block、缺失标签、未闭合 thinking 或不合格 stop reason 都会得到 null,不会猜测 326102-326140。
- Stage 1 明确 allow:立即返回,不再调用 Stage 2;
- fast-only 明确 block:直接阻断;
- 默认 both 下明确 block:进入 Stage 2 复核;
- Stage 1 无有效 verdict:默认 both 仍可进入 Stage 2,但失败状态会被保留用于后续错误归因。
Stage 2:给理由和分类的深判¶
Stage 2 输出预算为 8192 + thinking overhead,阶段外层超时 120 秒。它返回最终 block/allow,并可携带 <reason>、受客户端 allowlist 约束的 <category>、thinking 和 severity 326172-326177。
Stage 2 可以推翻 Stage 1 的 block 倾向而允许动作。反过来,如果 Stage 2 没形成有效 verdict,客户端不会凭空当作 allow:
| 失败形态 | 客户端结果 |
|---|---|
| Stage 2 parse failure | shouldBlock=true,带 failureMode,属于 no-verdict fail-closed |
| Stage 1 已 block,Stage 2 safeguard refusal | 保留 Stage 1 block 理由,避免 refusal 把已有阻断变成 allow |
| Stage 1 有有效 block、Stage 2 请求异常 | 以 Stage 1 assessment 阻断,并注明通常可重试 |
| Stage 1 无有效 verdict 但已有 usage、Stage 2 请求异常 | 返回 shouldBlock=true, unavailable=false,却没有 failureMode;后续被当普通 block,属于实现缺陷候选 |
| Stage 1 尚无结果、请求异常 | unavailable=true,普通工具 fail closed |
| transcript/context too long | transcriptTooLong=true,交给专用 fallback,而不是伪造风险 verdict |
请求内部默认 maxRetries=4,可由远端配置改为非负整数;XML 无法解析时也会在阶段预算内受限重试。某个 classifier beta 被 HTTP 400 拒绝时,客户端可在剩余阶段时间内去掉该 beta 重试;成功后为本 session 锁存“不要再带该 beta” 326219-326268。
unavailableOuterRetries 默认是 0,远端只接受 0-10;指数等待从 2 秒起,封顶 10 秒并带随机抖动 326310-326317、326410-326423。classifier model、按主模型映射和 fallback 候选也可以受远端配置及组织 model policy 影响;某账号在某一时刻究竟取到什么值,bundle 无法证明。
5. Fail closed 不等于“判定危险”¶
权限入口对 classifier 返回值做二次映射 395470-395501。必须把三类 denial 分开:
| 类型 | 条件 | 含义 | denial counter |
|---|---|---|---|
| 策略阻断 | 有明确 shouldBlock verdict,且非 unavailable/超长/refusal |
classifier 判断动作命中策略 | 计入 |
| unavailable | API、transport、abort 等导致没有 verdict | 为防误执行而拒绝,不是危险结论 | 不作为普通策略拒绝累计 |
| no verdict | 明确携带 failureMode 的 XML parse failure,或 safeguard refusal |
输出无法可靠解释或被独立 safeguard 拦截 | noVerdict=true,不按普通拒绝处理 |
这里存在一条不能被上表抹平的边缘路径。默认 two-stage 模式中,Stage 1 可以已经产生 usage、但 XML 没形成有效 verdict;随后 Stage 2 transport/request 抛异常。catch 分支因为 stage1Usage 存在,会返回 shouldBlock=true、unavailable=false,却既没有 failureMode,也没有 refusedBySafeguard 326159-326184。权限入口于是把它计入 consecutiveDenials/totalDenials,并构造没有 noVerdict 的 classifier denial 395470-395497。tool outcome 最终编码为 automode-blocked,而不是 automode-parsing-error 315769-315777、526399-526406。
这条路径没有可靠危险动作 verdict,不能解释为“分类器认定危险”。它是 2.1.235 的实现缺陷候选:失败语义丢失后误入普通策略拒绝的计数与编码。分析遥测或用户连续 denial 时必须把它与真正 policy block 区分。
失败回退按工具和运行模式细分:
- transcript too long +
Agent工具:直接 allow。fi在本版绑定为Agent115490,特殊分支位于 395474-395479。这不是“所有超长请求都放行”。 - AskUserQuestion: transcript 超长或 classifier unavailable 时回到真实 question dialog。
- headless/不可弹权限框: transcript 超长或 safeguard refusal 会 abort Agent,避免静默猜测。
dontAsk: 不能回退弹框时返回 deny。- 其余交互场景: transcript 超长可退回普通 permission handling;真正 unavailable 的普通工具直接 deny,并给重试指导。
tool-result 侧会把 denial kind 明确编码 315769-315777:
明确策略拒绝 -> automode-blocked
classifier 不可用 -> automode-unavailable
明确标记 noVerdict -> automode-parsing-error
这三个值随后进入 transcript,正常路径下下一次 classifier 可以区分“之前被判危险”和“之前只是系统没得出结论”。上文的 Stage 1 no-verdict + Stage 2 exception 边缘路径会错误落入 automode-blocked,所以 outcome code 不是绝对可靠的 ground truth。
6. PermissionDenied Hook 能做什么,不能做什么¶
拒绝结果已经构造后,只有 decisionReason.type=classifier 且 classifier=auto-mode 的 denial 会运行 PermissionDenied Hook 316295-316318。
Hook 可以看到工具名、tool-use ID、输入和拒绝理由。若任一 Hook 返回 retry:true,客户端给后续 Agent Loop 追加一条 meta 消息:The PermissionDenied hook indicated you may retry this tool call. 91994-92002。
边界必须说清:
- 原工具在本次 denial 中 没有执行;
- Hook 不能把 deny 改成 allow,也不会替用户点击审批;
retry:true只鼓励模型重新提议、缩小范围或换一种合法动作;noVerdict=true时即使 Hook 请求 retry,也不追加这条提示,避免把解析/可用性故障伪装成策略纠正;- Hook 自身失败不会倒转 denial。
7. /auto-mode-setup 如何避免“模型写完直接生效”¶
2.1.235 把配置建议和配置写入分成两个阶段。
交互流程¶
交互向导会先说明要读取当前项目、近期 Claude sessions,以及用户可选的 shell history/其他仓库;用户选择使用场景(work/open-source/hobby/mixed)和附加扫描范围后,扫描可以后台运行 467141-467156、467254-467279。
模型只能生成固定 JSON schema 的六个数组:environment、allow、soft_deny、hard_deny、remove_from_permissions_allow、notes 329159-329184。客户端随后:
- 校验 JSON shape;必要时只发起一次格式修复请求;
- 过滤过宽或不允许的 allow proposal;
- 验证待移除的
permissions.allow必须确实出现在本次扫描的 flagged 列表; - 把 proposal 固定标记为
mode=append并写入用户选择的scope; - 展示 Environment、Allow carve-outs、Soft blocks、Hard blocks、Notes;
- 只有用户选择 “Looks good - save it” 才写 settings;decline/cancel 都是零写入;
- 对危险或绕过 classifier 的旧
permissions.allow再单独询问是否移除,不能随 proposal 自动删除。
交互 review 和后台对话框路径见 467020-467039、467307-467346。
非交互 propose/apply 是 hash-bound 两阶段协议¶
非交互命令只允许:
/auto-mode-setup [--request-id UUID] --wizard posture=... scope=... depth=... --propose
/auto-mode-setup [--request-id UUID] [--apply-target user|project]
--expect-sha256 64_HEX --apply-file ABSOLUTE_PATH
参数顺序是语法合同:--request-id 必须第一,--apply-target 只能用于 apply-file,--expect-sha256 必须紧贴并位于 --apply-file 前;不存在一步完成的 --apply 329250-329323。canonical UUID 校验失败时不会回显非法 token 329238-329248。
apply 路径有以下写前约束 329188-329237:
- proposal 必须是系统临时目录或 Claude config 目录下的绝对路径;
permissions.deny的 read rule 可以阻止读取;- 读取使用
noFollow=true、requireNlink1=true,拒绝 symlink/hard-link 替换类风险; - 上限 1 MiB,并保留原始 bytes;
- SHA-256 对精确 bytes 计算,不是对 parse 后 JSON 计算;hash 不符时零写入;
- proposal schema、规则和移除列表再次校验;
--apply-target不决定真实写入位置,只校验 proposal scope 是否与用户/项目选择一致;最终始终写userSettings。
最终 settings 写入仍有第二层校验¶
写入函数 328728-328813 不信任 proposal 已校验过:
environment必须非空,且不能带$defaults;- 非空
allow/soft_deny/hard_deny必须包含$defaults,否则会意外替换 shipped rules; removeFromPermissionsAllow最多 200 条,每条必须是向导可产生的合法 rule string;append模式保留并去重已有 environment,规则按各自 slot 合并;- 合并后的完整
autoMode再过 schema; - environment 超过 200 项或约 50 KB时告警;整个 autoMode section 接近 settings 文件总上限的四分之一时告警;
- settings 文件不是合法 JSON、磁盘写入失败、合并后无效都返回结构化错误,不进行半可信写入。
8. 成本、延迟、隐私和调试字段¶
每次真正调用 classifier 都是独立模型请求,因此会增加延迟和 token 成本。classifyAllShell 会减少 shell allow fast path,通常增加 classifier 调用次数。客户端为 tengu_auto_mode_decision 和 session metric 记录:
- decision、tool、是否 MCP、fastPath;
- classifier model、stage、category、failure mode;
- Stage 1/2 各自 request/message ID、耗时、input/output/cache token;
- 总耗时、估算成本、queue depth/wait;
- system prompt/tool calls/user prompts 长度;
- consecutive/total denial 数;
- repo/protected namespace 等风险上下文。
字段装配见 395470-395497。这些字段能解释“为什么慢、在哪一阶段失败、是 policy block 还是 unavailable”,但它们不能证明模型内部推理正确。
隐私方面,classifier transcript 会包含对话、工具输入、用户配置的 environment 和选择性本地事实;/auto-mode-setup 还可读取项目、近期 sessions、shell history 命令词和其他仓库信息。因此环境规则应避免塞入不必要的秘密,企业部署也应把 classifier endpoint、日志和 retention 当作真实的数据处理面,而不是“纯本地权限判断”。
官方威胁模型与评测:为什么 Auto Mode 仍不是人工审批等价物¶
Anthropic 2026-03-25 的 How we built Claude Code auto mode 早于目标版本,提供了客户端静态代码无法恢复的设计背景。这里严格把它标为 Public:它解释威胁模型和当时内部评测,不等于 2.1.235 本机 Probe,也不证明当前账号的服务端 probe/classifier 配置。
官方描述的是两条防线:
工具输出 -> 服务端 prompt-injection probe -> 主 Agent context
用户消息 + Agent tool calls -> transcript classifier -> tool permission verdict
- 输入侧 probe扫描文件读取、WebFetch、shell和外部工具结果;发现疑似劫持内容时,在结果进入Agent context前增加警告。
- 输出侧classifier只看用户消息与Agent tool call;剥离assistant自我解释和tool result。这样Agent不能用自己的“这很安全”说辞说服审批器,恶意tool output也不会直接进入同一个审批判断。
- 子Agent递归经过同一管线;官方还描述delegation前可拒绝、结果返回后只警告的handoff检查。
这与本地 2.1.235 的“确定性权限前置层 -> 客户端classifier builder -> 两阶段XML verdict -> permission mapping”并不矛盾,但证据owner不同:本仓库可以确认本地输入裁剪、Stage 1/2、XML parser、denial counters和tool gate;服务端injection probe的实现与真实上线配置仍是Boundary。
官方评测公开了三组数据:
| Dataset | 样本 | Full pipeline 结果 | 应怎样理解 |
|---|---|---|---|
| Anthropic内部正常流量 | 10,000 | 0.4% FPR |
仍会误挡少量正常动作 |
| 真实overeager动作 | 52 | 17% FNR |
对“善意但越权”的漏判并不低 |
| 合成data-exfil任务 | 1,000 | 5.7% FNR |
对明确外传仍不是零漏判 |
Stage 1单独更偏向低漏判,完整Stage 2显著降低误报,但代价是漏掉更多危险动作。这组数字的价值不是宣传“安全率”,而是给使用边界定量:Auto Mode更适合替代用户已经形成的无脑批准习惯,不适合替代高风险生产基础设施的认真人工review。
官方还说明连续拒绝会升级到人工介入;本版client确实记录 consecutiveDenials/totalDenials 并把不同失败种类映射到permission surface。是否采用“连续3次或累计20次”的远端/产品策略仍必须按本地config consumer和运行时值判断,不能只靠文章数字覆盖目标bundle。
这组Public事实也强化了本文的核心结论:fail-closed只说明没有可靠allow,不说明动作一定危险;即使得到allow,permission之后仍有sandbox、credential和外部owner边界。
9. 能确认与不能确认的边界¶
Static:2.1.235 客户端可以确认¶
- Auto Mode 的权限位置、前置 floor、快速路径和 mode-change 重检;
- 四段规则、可信来源、数组追加、
$defaults插入和classifyAllShell; - transcript/action/meta 构造和 outcome 语义;
- 两阶段请求预算、XML parser、timeout、retry、beta 降级和 fail-closed;
- Stage 1 no-verdict + Stage 2 exception 被误编码为普通 block 的缺陷候选;
- Agent/AskUserQuestion/headless/dontAsk 的不同 fallback;
- PermissionDenied Hook 只追加重试提示;
/auto-mode-setup的 proposal/review/hash-bound apply/settings 写入保护;- 客户端发出的 telemetry 字段。
Boundary:发布 bundle 不能确认¶
- classifier 模型在真实请求上的判断质量、误报率和漏报率;
- 模型内部如何把 prompt 转成 verdict;
- 服务端 safeguard、账户风控、abuse detection 和封禁策略;
- 某个账号当前拿到的远端 model mapping、threshold、retry 或 two-stage 配置值;
- 未执行运行探针时,各 provider/gateway 对 classifier beta、prompt cache 和请求字段的真实兼容性;
- bundle 之外被删去的原始 TypeScript、服务端源码和训练数据。
最后用一句话收口:Auto Mode 提高的是“普通审批可自动化”的范围,不会取消确定性权限、managed policy、Hook、sandbox 或真实操作系统/远端权限;而 fail-closed 只说明客户端没有得到可安全采用的 allow,不代表模型已经判定动作危险。