Claude Code Auto Mode:权限系统里的 AI Classifier 层

面向 agent / harness 工程师,拆解 Claude Code 2.1.193 的 auto mode 权限链路、classifier 上下文、两阶段判定、fail-closed 语义和 host 集成边界。

这篇文章面向 agent / harness 工程师,也面向会用 agent 阅读技术文章的人。重点放在 auto mode 在权限系统里的位置:谁提出动作,谁判断权限,classifier 看到了什么,什么情况下它没有资格判断,失败时怎么处理,以及这些设计对自己的 agent runtime 有什么借鉴价值。

以下分析基于本机安装的 Claude Code 2.1.193。信息来源包括 claude auto-mode defaults / claude auto-mode config 暴露的配置、CLI 行为、打包产物里的可观察字符串和混淆后的执行链路。文中会用伪码和结构化图表达机制,不贴大段实现代码。

Executive Summary

  • Auto mode 是 Claude Code 权限系统里的 classify 模式。它接管一部分原本会弹 permission prompt 的 ask 请求。
  • 主模型只提出 tool_use。最终能不能执行由 Claude Code 的权限引擎决定;auto classifier 只是权限引擎可调用的一个审批组件。
  • 旧的 deterministic 权限层仍然先跑:allow / deny / ask rules、工具自己的 checkPermissions、sandbox、路径安全、MCP 上限、组织策略、hook 等都在 classifier 之前。
  • Classifier 只处理“普通权限检查返回 ask,并且这个 ask 可以被 classifier 接管”的灰区。
  • 当前版默认是 XML 两阶段 classifier:Stage 1 快速给 <block>yes/no</block>,明确安全时直接放行;疑似风险或不确定时进入 Stage 2,用更长预算输出 <block><reason>
  • Classifier 没有主循环完整上下文。它拿到的是权限判断专用上下文:当前 action、裁剪后的 transcript、tool outcome、auto-mode rules、settings deny rules、CLAUDE.md、repo / branch / user identity 等。
  • 交互式 CLI 下,classifier 不能处理时常回退到用户确认;headless / background agent 下通常 fail closed,表现为 deny 或 abort。
  • Host / SDK 需要区分两类事件:can_use_tool 是“请用户或 host 决策”;permission_denied 是“引擎已经短路拒绝,请渲染和记录”。
  • 对 harness 设计的核心借鉴:用代码处理确定性边界,用 classifier 处理上下文语义灰区,输出窄协议,失败保守,所有上下文输入有硬上限。

Mental Model

把 auto mode 想成权限系统里的“自动审批员”。它不承担 agent 主脑职责,也不等同于 bypass permissions。

主 Claude 做的事情是生成工具调用:

我想执行 Bash("git push origin main")

Claude Code 权限引擎接住这个工具调用,按规则判断:

这个动作已被允许 -> 执行
这个动作已被拒绝 -> 拒绝
这个动作需要确认 -> 看当前 permission mode

如果当前 mode 是 auto,这个“需要确认”的动作可能进入 classifier。Classifier 的任务很窄:

这次工具调用能自动放行吗?

它输出 block=noblock=yes。权限引擎再把结果变成 allow / deny / fallback ask。

flowchart TD Main["Main Claude proposes tool_use"] --> Engine["Claude Code permission engine"] Engine --> Rules["Rules, sandbox, tool checkPermissions"] Rules -->|allow| Run["Run tool"] Rules -->|deny| Deny["Deny before prompt"] Rules -->|ask| Mode["Permission mode"] Mode -->|default| Prompt["Interactive can_use_tool prompt"] Mode -->|dontAsk| Deny Mode -->|bypassPermissions| Run Mode -->|auto| AutoGate["Auto-mode eligibility gates"] AutoGate -->|not classifier-eligible| Prompt AutoGate -->|headless and needs human| Deny AutoGate -->|eligible| FastPath["acceptEdits fast path + safe allowlist"] FastPath -->|allow| Run FastPath -->|miss| Classifier["Auto-mode classifier"] Classifier -->|block=no| Run Classifier -->|block=yes| Deny Classifier -->|unavailable or too long| Failure["Fallback ask or fail closed"]

Permission Mode Mapping

Claude Code 内部把 permission mode 映射成默认行为。用伪码表达大概是:

function behaviorForMode(mode, planCanBypass) {
  if (mode === "auto") return "classify"
  if (mode === "bypassPermissions") return "allow"
  if (mode === "plan" && planCanBypass) return "allow"
  if (mode === "dontAsk") return "deny"
  return "ask"
}

这说明 auto 是一个独立语义:classify。它和 acceptEditsbypassPermissionsdontAsk 都不同。

Mode默认权限语义典型行为
defaultask不确定时问用户
acceptEdits局部自动接受文件编辑类更容易放行,仍保留其他检查
autoclassify不确定时先走 auto classifier
bypassPermissionsallow跳过大多数确认,用于显式危险模式
dontAskdeny需要确认的动作直接拒绝
plan取决于 plan / auto 配置规划态下限制执行,退出计划时恢复前置模式

Auto mode 的产品语义可以写得很短:减少低风险操作对用户的打断,同时保留权限系统的硬边界。

Gate Access

Auto mode 自己还有入口 gate。当前版本里能看到这些拒绝原因:

拒绝原因含义
settingssettings 里 disableAutoMode 禁用了 auto mode
circuit-breaker远端配置把 auto mode 关掉,或者套餐/计划不可用
provider当前 provider 没开启 auto mode,需要显式环境开关
model当前 model 不支持 auto mode

有几个重要 settings / config 信号:

名称作用
disableAutoMode禁用 auto mode
useAutoModeDuringPlanplan mode 下是否使用 auto mode 语义,默认描述为 true
autoMode用户是否接受过 auto mode opt-in dialog
stripAllBashFlagauto mode 下挂起 Bash / PowerShell allow rule,让更多 shell 命令进入 classifier
tengu_auto_mode_config.enabled远端配置的 enabled / disabled / opt-in 状态
modelByMainModel按主模型映射 classifier 模型
fallbackModelByModelclassifier 主模型不可用时的 fallback 模型
twoStageClassifierboth / fast / thinking
sameTurnSiblingContext是否把同一轮相邻 tool uses 带入 classifier context
jsonlTranscriptclassifier transcript 的另一种编码开关

进入 auto mode 时,Claude Code 还会处理可能绕过 classifier 的危险 allow rule。宽泛的 Bash / PowerShell allow rule、某些危险 shell 权限,会被记录和剥离。退出 auto mode 时再恢复这些规则。这个细节很关键:auto mode 不能一边说“让我判定 shell 命令”,一边保留一个 Bash(*) allow rule 把所有 shell 命令提前放走。

Normal Permission Layer

Classifier 之前的普通权限链路可以拆成几类。

1. User / policy rules

包括 allow、deny、ask 三类规则,来源可以是 settings、CLI 参数、session、project policy、MCP server policy 等。

决策顺序上,deny 和 ask 可以在 tool 自身检查之前触发。典型效果:

  • deny rule 命中,直接 deny。
  • ask rule 命中,进入 ask。
  • allow rule 命中,可能 allow,但 Chrome classifier floor 等特殊配置可以要求继续让 classifier 判。

规则匹配也考虑 tool alias、MCP server 级规则、specifier 字段等。比如 WebFetch(domain:example.com)Bash(npm test) 是不同 matcher 语义。

2. Tool checkPermissions

每个工具可以声明自己的权限检查逻辑。Bash、Edit、Write、Read、WebFetch、MCP tool 都可能根据输入返回:

allow / ask / deny / passthrough

例如文件写入会走路径安全检查;Bash 会做命令解析和危险子命令检测;WebFetch 会看 domain 权限。

3. Safety checks

文件路径安全检查里可以看到一类非常重要的字段:classifierApprovable

有些安全检查可以让 classifier 接管,有些必须人工确认。比如下面这些更偏硬边界:

  • UNC network path 需要人工确认。
  • ~user~+~- 这类 tilde expansion 变体需要人工确认。
  • 路径里出现 shell expansion 语法需要人工确认。
  • 目录 segment 之后出现 .. traversal,可能穿过 symlink 跳出工作目录。
  • 写操作不能用 glob pattern,必须给 exact path。
  • sandbox override 通常不能交给 classifier 自动放。

这就是 classifierApprovable 的价值:权限系统可以把“模型可判断的语义风险”和“必须由确定性逻辑或人工处理的底层安全风险”分开。

4. MCP and org ceilings

MCP tool 可以有 effective max permission。若上限是 ask,auto mode 不能越权放行。

组织策略也可以把某些工具压到 ask ceiling。这个 ask ceiling 会覆盖 auto classifier。

Auto-Mode Decision Path

普通权限检查返回 ask 后,auto mode 会做第二层筛选。

flowchart TD Ask["Normal permission result = ask"] --> DontAsk["mode == dontAsk?"] DontAsk -->|yes| Deny["deny"] DontAsk -->|no| Auto["mode == auto or Chrome floor?"] Auto -->|no| Prompt["normal prompt"] Auto -->|yes| Gates["classifier eligibility gates"] Gates -->|safetyCheck classifierApprovable=false| Prompt Gates -->|sandboxOverride| Prompt Gates -->|ask rule| Prompt Gates -->|MCP max ask or org ask ceiling| Prompt Gates -->|requires user interaction| Prompt Gates -->|workflow usage consent| Prompt Gates -->|headless cannot prompt| Deny Gates -->|eligible| AcceptEdits["simulate acceptEdits permission context"] AcceptEdits -->|allow| Allow["allow, fastPath=acceptEdits"] AcceptEdits -->|ask/deny/error| Allowlist["safe allowlist"] Allowlist -->|hit| Allow Allowlist -->|miss| Classifier["call classifier"]

acceptEdits fast path

在真正调用 classifier 之前,Claude Code 会尝试用类似 acceptEdits 的上下文重新跑工具自己的 permission check。它会过滤掉危险 classifier-bypass rule,避免 Bash(*) 这类规则污染结果。

如果这次检查得到 allow,就直接放行,并记录 fastPath=acceptEdits

这个 fast path 的意义很清楚:许多低风险动作无需消耗 classifier 调用。比如项目内编辑、常规本地操作、部分 read-only 行为,工具自身已经有足够规则处理。

safe allowlist

acceptEdits fast path 没命中后,还有一层 safe allowlist。命中时同样跳过 classifier,记录 fastPath=allowlist

文章不展开具体 allowlist 项,因为它属于内部实现细节,版本变动概率很高。设计上它承担的是低成本、高置信的短路通道。

Classifier Input

Classifier 拿到的是为权限审批裁剪出来的上下文包,而非主循环完整上下文。

flowchart LR Messages["Main loop messages"] --> Extract["Transcript extractor"] Tools["Tool registry"] --> Action["toAutoClassifierInput"] Rules["auto-mode config"] --> Prompt["Classifier system prompt"] Settings["deny rules"] --> Prompt ClaudeMd["CLAUDE.md"] --> Prompt Env["repo, branch, user identity"] --> Prompt Extract --> Transcript["Bounded transcript"] Action --> Current["Current action under review"] Transcript --> Request["Classifier request"] Current --> Request Prompt --> Request

Current action

当前要审批的 action 会被转成 classifier 专用输入。每个工具可以实现自己的 toAutoClassifierInput

这一步很重要。Classifier 不应该直接看任意 JSON blob,然后自己猜字段语义。工具应该把与权限相关的内容提取出来:

Bash git push origin main
Edit path + patch intent
WebFetch target domain / URL
SandboxNetworkAccess host + port
Agent handoff summary

如果工具声明没有 classifier-relevant input,classifier 可以直接认为无需阻断。

Bounded transcript

Transcript extractor 会从主消息流里抽取相关 user text、assistant tool_use、部分 tool_result、用户回答、任务通知等。它会跳过一些对权限判断意义低或体积大的工具调用,避免 classifier context 变成主循环镜像。

在调试日志里能看到 Claude Code 专门比较:

mainLoopTokens
classifierChars
classifierTokensEst
transcriptEntries
messages

这说明主循环上下文和 classifier 上下文是两份对象。Classifier 的上下文是派生物,有自己的长度估算、裁剪、编码和 cache control。

Tool outcome annotations

前序工具调用会携带 outcome 语义。当前版本能看到这些类型:

Outcome含义
okharness 层认为调用执行成功
errorharness 层错误,调用可能没有真正执行
interrupted用户中断,副作用可能部分发生
rejected-by-user用户拒绝过这个动作
blocked-by-permissions普通权限规则拒绝
automode-blockedauto classifier 主动拒绝
automode-unavailableclassifier 不可用,fail-closed 挡下
automode-parsing-errorclassifier 响应无法解析,fail-closed 挡下

这让 classifier 能识别绕过行为。例如用户刚拒绝了一个 git push main,主模型换个 shell 写法重试,classifier 仍能看到“用户拒绝过类似动作”。

Auto-mode rules

claude auto-mode defaults 暴露了四类规则。当前本机 configdefaults 数量一致:

SectionCount作用
environment5定义信任边界
allow11通常可以自动放行的模式
soft_deny33高风险,通常 block 或 require confirmation
hard_deny1必须阻断的核心风险

默认 environment 的核心是:信任当前 git repo、启动目录、配置 remote;额外内部域名、cloud bucket、关键服务默认都没有配置。

默认 allow 覆盖这些方向:

  • transient retry。
  • test artifact / placeholder credential。
  • 项目范围内 local operations。
  • read-only operation。
  • 已在 manifest 声明的依赖安装。
  • 官方 toolchain bootstrap。
  • 标准 credential 发往 intended provider。
  • push 到当前工作分支或本 session 创建的分支,不包括默认分支。
  • agent memory directory 常规写入。
  • 不改变权限语义的 CLAUDE.md 内容编辑。
  • Claude Code scheduling 任务管理。

默认 soft_deny 覆盖的风险面更大:

  • git push --force、删除远端分支、改写历史。
  • 直接 push 到 main / master / default branch。
  • 下载外部代码并执行,例如 curl | bash
  • cloud storage mass delete。
  • production deploy / migration。
  • prod / shared host remote shell read or write。
  • live infra blind apply。
  • logging / audit tampering。
  • 权限提升、IAM / RBAC grant。
  • TLS / auth / security weakening。
  • 创建不安全 autonomous loops。
  • 干扰非本 session 创建的 workload。
  • 修改共享资源。
  • 不可逆本地破坏。
  • 创建 RCE surface。
  • 暴露本地服务或 host path。
  • credential leakage / credential exploration。
  • exfil scouting / unverified destination。
  • live shared artifact sensitive delta。
  • sandbox network callback。
  • 创建公开 surface 或 public data-sharing upload。
  • 引入不可信代码并安排执行。
  • unauthorized persistence。
  • self-modification 和 instruction poisoning。
  • 绕过 auto mode 或权限系统。
  • 外部协作系统写操作。
  • 真实世界交易。

默认 hard_deny 是 data exfiltration:敏感数据跨出信任边界,或者武装一个之后会外传的自动路径。

CLAUDE.md and user intent

Classifier 会看到 CLAUDE.md。但当前 prompt 里有一个明确原则:泛泛的鼓励不构成授权。比如:

be autonomous
don't ask
I trust you

这些不会降低阻断阈值。只有针对同一操作、同一目标的明确授权,才可能作为 user intent 参与判断。

Settings deny rules

用户配置的 deny rules 会进入 classifier prompt。Classifier 被要求识别“绕过 deny rule”的行为。

典型例子:

用户 deny 了 Edit(path)
主模型改用 python -c / sed -i / heredoc 写同一个 path

这类绕过不应因为换了工具表面形态就被自动放行。

Session identity and branch semantics

Classifier prompt 里会注入用户身份,用于解析 $USER/... 这类 branch pattern。

例如 $USER/feature-x 可能是当前用户个人分支;other-user/feature-x 就不是。这个信号直接影响“push 到工作分支是否可放行”的判断。

Classifier Does Not Have Full Main Context

对 harness 工程师来说,这一点要单独强调。

Classifier 没有:

  • 主模型完整 system prompt。
  • 主模型隐藏 thinking。
  • 完整原始 transcript。
  • 所有 tool_result 原文。
  • 完整工具 schema。
  • 完整附件和展示层内容。
  • 主循环真实 cache / context 结构。

Classifier 有:

  • 当前待审批 action。
  • 从主消息流派生的 bounded transcript。
  • 相关 tool_use 和 outcome。
  • 权限规则和信任边界。
  • CLAUDE.md 和 settings deny rules。
  • repo / branch / user identity。
  • 可选 same-turn sibling tool uses。

这个边界让 classifier 更接近 policy engine 的模型后端,而非另一个完整 agent。

XML Two-Stage Classifier

当前版本默认分类器类型接近 xml_2stage。也可以通过配置走 xml_fastxml_thinking

sequenceDiagram participant Engine as "Permission engine" participant S1 as "Stage 1 fast classifier" participant S2 as "Stage 2 thinking classifier" Engine->>S1: "system rules + transcript + current action" S1-->>Engine: "<block>no</block> or <block>yes</block>" alt "Stage 1 returns block=no" Engine->>Engine: "allow without Stage 2" else "Stage 1 returns block=yes or parse ambiguous" Engine->>S2: "same case + thinking suffix" S2-->>Engine: "<block>yes/no</block><reason>...</reason>" end

Stage 1 的目标是快速初筛。它的输出预算很小,甚至可以用 stop sequence 在 </block> 附近提前截断。若 Stage 1 明确 block=no,权限引擎直接 allow。

Stage 2 的目标是复核和解释。它有更大的 output budget,可以输出 thinking 和 reason。最终解析规则很窄:

<block>yes</block>
<reason>...</reason>

或:

<block>no</block>

解析逻辑会去掉 <thinking> 后查找第一个 <block>yes/no</block>。找不到 block 就是 parse failure。

Retry and parse failure

Classifier 调用会对不可解析输出做有限重试。失败类型包括:

  • policy_refusal
  • unparseable
  • parse_failure
  • max_tokens
  • transcript_too_long
  • API / connection / timeout 错误

解析失败不会默默放行。一般会被当成 block,按 fail-closed 路径处理。

Model selection and fallback

Classifier model 可以来自:

  • 当前主模型。
  • modelByMainModel 映射。
  • auto-mode config 里的显式 model
  • 某些模型家族的内部映射。

如果主 classifier model 因 transient 错误不可用,且配置了 fallbackModelByModel,会在剩余时间预算内尝试 fallback model。可重试的不可用错误包括 timeout、connection error、429、5xx 等。

Temperature and prompt customization

CLAUDE_CODE_AUTO_MODE_TEMPERATURE 可以覆盖 classifier temperature。auto-mode config 也支持 stage suffix 和 prompt customization。

这意味着 auto mode 的安全策略并非硬编码在一个 if-else 表里。它采用“规则 prompt + deterministic gate + classifier backend”的组合。

Decision Mapping

Classifier 返回值进入权限系统后,会映射成 tool permission result。

Classifier resultPermission behaviorReason
shouldBlock=falseallowtype=classifier, classifier=auto-mode
shouldBlock=true 且可解释denytype=classifier, reason=<reason>
transcript too long, interactivefallback ask提示 compact 后重试
transcript too long, headlessabort无法弹窗确认
classifier unavailabledenyfail closed,提示稍后重试
sandbox network classifier unavailabledenysandbox network fail closed
user abortunavailable / deny中止请求

如果 classifier block,权限引擎会把 reason 反馈给主模型。主模型可以继续做其他不依赖该动作的任务,或者调整方案。若 PermissionDenied hook 指示可 retry,模型会收到“可以重试”的信号。

Denial Tracking

Auto mode 有 denial tracking。成功 allow 会重置连续拒绝计数;block 会增加连续和总拒绝数。

当连续或总拒绝超过阈值:

  • interactive CLI:回退到 prompt,并提示用户检查 transcript。
  • headless / background agent:abort,避免 agent 在无人值守状态下持续撞权限墙。
stateDiagram-v2 [*] --> Normal Normal --> DeniedOnce: "classifier blocks" DeniedOnce --> DeniedMany: "more blocks" DeniedMany --> PromptUser: "interactive limit exceeded" DeniedMany --> AbortAgent: "headless limit exceeded" DeniedOnce --> Normal: "allowed action resets consecutive count" DeniedMany --> Normal: "allowed action resets consecutive count"

这个机制避免主模型在被 auto mode 拒绝后,通过不断改写命令来 brute-force classifier。

Headless And Background Agents

Headless / background 模式是 auto mode 设计里最容易被低估的部分。

在 interactive CLI 里,classifier 不能处理时还可以问用户。Headless agent 没有这个出口,所以它更保守:

  • 需要人工确认的动作直接 deny。
  • classifier transcript 超上下文直接 abort。
  • classifier 连续 deny 过多直接 abort。
  • classifier unavailable 时 fail closed。
  • --bg 搭配 auto mode 需要用户先交互式 opt in。

这对于 harness 很重要:同一个 permission mode 在不同 host capability 下应该有不同 failure semantics。能弹窗的 host 可以 fallback ask;不能弹窗的 host 必须明确 deny / abort / pause。

Sandbox Network Access

Sandboxed Bash 运行时若尝试 outbound connection,会触发类似 SandboxNetworkAccess 的中途权限判断。Auto classifier 会看到 host 和 port。

如果 classifier 不可用,这条路径会 fail closed deny。原因是 sandbox network callback 的 payload 不包含完整出站内容,权限系统只能看到目的地,不能看到真正发送的数据。目的地不可信时,风险要按数据外传准备动作处理。

Chrome / Browser Floor

当前版本还有 Chrome MCP tool 相关的 classifier floor。

一般情况下,如果 Chrome domain 已被允许,工具可以按规则放行。开启 Chrome classifier floor 后,即使 domain 规则允许,也可能继续让 classifier 判断。这适合 browser action 风险更高的场景,因为页面状态、登录态、表单提交、真实账户副作用并不总能从 domain allow rule 看出来。

Plan Mode Interaction

useAutoModeDuringPlan 描述为 plan mode 可用时默认使用 auto mode 语义。内部有 prePlanMode,用于记录进入 plan 前的模式,并在退出 plan 时恢复。

计划相关 gate 里有 plan_mode_floor。当某个权限 ask 来自 plan mode floor,classifier 不会越过它。

这说明 auto mode 并不会把 plan mode 变成可执行模式。它只是影响 plan mode 周边的 permission semantics,尤其是退出计划、恢复执行、提示用户时的路径。

Dynamic Workflow And Subagent Handoff

当前版本里 auto mode 还被用于 subagent handoff 审查。大概流程是:

subagent finished -> main agent receives handoff -> auto classifier reviews subagent actions/output

如果 classifier 判断 subagent 做过可能违反安全策略的动作,会给 main agent 一个安全警告。若 handoff classifier 不可用,当前行为更偏向“允许输出但带 warning”,因为 subagent 已经跑完,阻断 handoff 反而可能让主 agent看不到需要审查的信息。

这和普通 tool preflight 的 fail-closed 不同。普通 tool 还没执行,可以安全地挡住;handoff 是 post factum 审查,更合理的动作是附带风险标记交给主 agent 复核。

flowchart TD Sub["Subagent completes"] --> Review["Handoff classifier"] Review -->|allowed| Main["Return result to main agent"] Review -->|blocked| Warn["Attach security warning"] Review -->|unavailable| SoftWarn["Return result with unavailable warning"] Warn --> Main SoftWarn --> Main

Host / SDK Surface

Host 层要处理两条完全不同的权限通道。

can_use_tool

这是 ask 路径。引擎把待确认的工具调用发给 host:

control_request: can_use_tool

Host 可以渲染 UI,或用自己的 policy/hook 决策。返回 allow / deny / updatedInput / permissionUpdates。

permission_denied

这是 deny short-circuit 路径。引擎已经决定拒绝,不再向 host 请求决策:

system: permission_denied

字段包括:

Field作用
tool_name被拒绝的工具
tool_use_id对应 tool use
agent_id若来自 subagent,用于 host 路由
decision_reason_typeclassifier / asyncAgent / mode / rule
decision_reason人可读原因
message返回给模型的拒绝消息

Host 需要渲染这个事件,否则用户只能在 tool result error 里间接看到失败。

flowchart LR Engine["Permission engine"] -->|ask| Control["control_request: can_use_tool"] Control --> Host["Host UI / SDK client"] Host -->|allow or deny| Engine Engine -->|short-circuit deny| Event["system: permission_denied"] Event --> Host Event --> ModelResult["is_error tool_result to model"]

Hooks

Auto mode 和 hooks 有三处交叉。

PermissionRequest

PermissionRequest hook 可以在 ask 路径上程序化 allow / deny。若 hook allow 后修改了 updatedInput,权限系统会重新检查新的 input,避免 hook 绕过规则。

PermissionDenied

Auto mode classifier deny 后,会触发 PermissionDenied hook。输入包含:

{
  "tool_name": "...",
  "tool_input": {},
  "tool_use_id": "...",
  "reason": "..."
}

Hook 可以返回:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

这会告诉模型这个 tool call 可以重试。注意它不放行工具调用,只给模型一个“可以换策略或稍后再试”的信号。

PreToolUse / PostToolUse

PreToolUse hook 可以在更早阶段 block。它绕过 canUseTool,因此不属于 auto mode classifier 的 permission_denied 事件覆盖范围。

对 harness 的实现建议:把 hook deny、rule deny、classifier deny、headless deny 分成不同 reason type,别把它们都拍平成 permission denied

Observability

当前版本的 auto mode 记录了大量 telemetry / debug 信号。对自研 harness 来说,这些字段值得参考。

Category示例字段
classifier outcomeallowed / blocked / unavailable / parse_failure / transcript_too_long
classifier modelclassifierModel、fallbackFrom、stage
cost and usageinputTokens、outputTokens、cacheReadInputTokens、costUSD
latencydurationMs、stage1DurationMs、stage2DurationMs
prompt sizesystemPromptLength、toolCallsLength、userPromptsLength
context comparisonmainLoopTokens、classifierChars、classifierTokensEst
decision metadatafastPath、originalDecisionReasonType、sameTurnSiblings
denial trackingconsecutiveDenials、totalDenials
failurehttpStatus、errorKind、failureMode、errorDumpPath

Debug 模式下还会 dump classifier error prompts 到 auto-mode classifier error 目录,方便排查 prompt 太长、parse failure、API error。

Failure Semantics Matrix

FailureInteractive CLIHeadless / background原因
classifier unavailabledeny,提示稍后重试或做其他任务deny / fail closed不能因为 safety backend 挂了就放行
transcript too longfallback ask,建议 compactabort无法弹用户确认
parse failurefail closedfail closed窄协议解析失败不能默认安全
requires user interactionfallback askdenyheadless 无交互通道
ask rulefallback askdeny用户/策略明确要求问人
org ask ceilingfallback askdenyclassifier 不能越过组织上限
denial limit exceededfallback promptabort防止无人值守循环撞墙
sandbox network unavailabledenydeny中途外连风险缺少 payload 可见性

这个矩阵的设计比“统一抛异常”成熟很多。它按 host capability 区分恢复策略。

Security Boundary

Auto mode 的安全边界由几层共同构成。

Deterministic controls

  • 文件路径解析和 canonicalization。
  • 工作目录和 sandbox write allowlist。
  • allow / deny / ask rules。
  • MCP max permission。
  • org policy。
  • dangerous command parser。
  • permission mode gate。
  • headless prompt availability。

Model classifier controls

  • 规则 prompt。
  • bounded transcript。
  • current action normalization。
  • XML narrow output protocol。
  • two-stage classification。
  • parse retry。
  • fail closed。

Operational controls

  • denial limit。
  • telemetry。
  • error prompt dump。
  • PermissionDenied hook。
  • UI recent denials。
  • background agent abort semantics。

这三层组合后,auto mode 才能做到“减少打断”同时不变成“自动放行”。

What Classifier Can And Cannot Decide

适合 classifier 判断的情况:

  • 当前动作是否符合用户刚刚表达的具体意图。
  • 一个重试是否只是 transient retry,还是在绕过拒绝。
  • 某个 shell 命令实际会不会把数据发到不可信边界。
  • 一个 git push 是当前工作分支,还是默认分支或他人分支。
  • 一个本地删除是项目内可恢复清理,还是不可逆破坏。
  • 一个外部工具调用是 read-only,还是会修改共享系统。
  • 一个 CLAUDE.md 编辑是普通偏好,还是 permission grant / instruction poisoning。

不适合 classifier 单独决定的情况:

  • 文件路径 canonicalization。
  • symlink / traversal / UNC path 安全。
  • sandbox override。
  • 组织策略上限。
  • MCP server policy 要求 ask。
  • 需要真实用户交互或 consent。
  • host 没有能力承接失败恢复。

Harness 设计里要把这两类边界编码成不同字段。classifierApprovable 这类 flag 非常值得抄。

Compare With Simple Auto-Allow

Auto mode 容易被误解成“AI 替你点 Yes”。实际结构复杂得多。

简单 auto-allow 的流程:

if mode == auto:
  allow()

Claude Code auto mode 的流程:

normal rules
tool checkPermissions
safety checks
auto eligibility gates
fast path
classifier
failure semantics
host event projection

前者是权限绕过。后者是权限系统里的模型审批后端。

Harness Design Patterns

1. Keep the main agent out of final authority

主模型可以提出动作、解释意图、修正方案。最终 allow / deny 应该由 runtime 执行。

推荐结构:

model proposes -> engine validates -> policy decides -> tool runs

避免结构:

model says it is safe -> tool runs

2. Normalize action before classification

不要把 raw tool JSON 直接交给 classifier。每个工具应该提供 classifier input projection。

例如:

interface Tool {
  checkPermissions(input, context): PermissionResult
  toClassifierInput(input): string | object | ""
}

这让 classifier 看见“权限相关语义”,减少无关字段干扰。

3. Preserve outcome semantics

Tool result 不只需要 stdout / stderr。权限 classifier 需要知道前序动作的 harness-level outcome。

type ToolOutcome =
  | "ok"
  | "error"
  | "interrupted"
  | "rejected-by-user"
  | "blocked-by-permissions"
  | "automode-blocked"
  | "automode-unavailable"
  | "automode-parsing-error"

这能防止模型把“被挡住”误读成“没发生过”,也能防止它把“上次 ok”当成未来同类动作的永久先例。

4. Add a classifier-approvable bit

安全检查不要只返回 ask。应该返回:

{
  behavior: "ask",
  reason: "...",
  classifierApprovable: false
}

这允许 runtime 明确表达:“这里要问人,不允许模型审批。”

5. Separate ask and deny surfaces

Host 需要两个通道:

ask: control_request(can_use_tool)
deny: system(permission_denied)

Ask 是 host 决策点。Deny 是 engine 已决策事件。两个通道混在一起会让 UI、SDK、audit log 都变模糊。

6. Fail closed with host-aware fallback

失败语义要看 host capability。

if (classifierUnavailable) {
  if (host.canPromptUser) return askUser()
  return denyOrAbort()
}

Headless agent 的安全策略应该更保守,因为它没有人类实时纠偏。

7. Budget classifier context independently

Classifier context 要独立估算和裁剪。主循环能跑,不代表 classifier context 也能塞下。

建议显式记录:

  • main loop token estimate。
  • classifier char count。
  • classifier token estimate。
  • transcript entry count。
  • system prompt length。
  • current action length。

8. Make rules inspectable

Claude Code 提供:

claude auto-mode defaults
claude auto-mode config
claude auto-mode critique

这对规则作者和企业 admin 都重要。Policy-as-prompt 如果不可见、不可 critique,很快会变成黑箱。

Minimal Reference Architecture

下面是一个可复用的 auto-mode harness skeleton。

flowchart TD ToolUse["Tool use"] --> RuleEngine["Rule engine"] RuleEngine --> ToolPolicy["Tool checkPermissions"] ToolPolicy -->|allow| Execute["Execute"] ToolPolicy -->|deny| EmitDeny["Emit permission_denied"] ToolPolicy -->|ask| ModeRouter["Mode router"] ModeRouter -->|default| AskHost["Host can_use_tool"] ModeRouter -->|dontAsk| EmitDeny ModeRouter -->|auto| Eligibility["Classifier eligibility"] Eligibility -->|manual only| AskOrDeny["Ask if possible, else deny"] Eligibility -->|eligible| ActionProjection["toClassifierInput"] ActionProjection --> ContextBuilder["Build bounded classifier context"] ContextBuilder --> Classifier["Classifier backend"] Classifier --> Parser["Narrow protocol parser"] Parser -->|allow| Execute Parser -->|block| EmitDeny Parser -->|error| FailClosed["Fail closed / fallback ask"]

关键接口可以长这样:

type PermissionBehavior = "allow" | "ask" | "deny"

type PermissionDecisionReason =
  | { type: "rule"; rule: Rule }
  | { type: "safetyCheck"; reason: string; classifierApprovable: boolean }
  | { type: "sandboxOverride"; reason: string }
  | { type: "classifier"; classifier: "auto-mode"; reason: string }
  | { type: "asyncAgent"; reason: string }
  | { type: "mode"; mode: PermissionMode }
  | { type: "other"; reason: string }

type PermissionResult = {
  behavior: PermissionBehavior
  updatedInput?: unknown
  message?: string
  decisionReason?: PermissionDecisionReason
}

Practical Reading Of Default Policy

默认 policy 的哲学可以概括成几条:

  1. 当前 repo 和它的 remote 是信任边界核心。
  2. 读操作一般低风险,但 URL 里带敏感信息、外传准备动作、上下文明确有危险意图时仍要挡。
  3. 项目内本地文件操作可以自动化,但不可逆删除、跨工作目录、shell expansion、symlink traversal 要谨慎。
  4. 依赖安装只有“manifest 已声明”才偏安全;外部下载并执行属于高风险。
  5. Git push 只对工作分支或本 session 创建分支相对安全;default branch、force push、删除远端分支是高风险。
  6. 生产、共享资源、真实世界交易、权限提升、审计绕过都不应由 auto classifier 悄悄放行。
  7. 数据外传是 hard deny 方向,不只是“上传文件”,也包括创建之后会外传的自动路径。

这套 policy 面向 coding agent 很实际:大部分日常开发不打断,跨 trust boundary 的动作强约束。

Edge Cases Worth Testing

如果你要实现类似系统,至少要测这些 case:

  • 用户 deny 了 Edit(foo),agent 改用 python -cfoo
  • 用户拒绝过 git push main,agent 稍后改成 git push origin HEAD:main
  • curl https://example.com/install.sh | bash
  • 读取 .env 后向未验证 webhook POST。
  • rm -rf node_modulesrm -rf ~/Documents 的差异。
  • project branch push 与 default branch push 的差异。
  • classifier API timeout。
  • classifier parse failure。
  • transcript too long。
  • headless agent 中需要用户确认的 ask rule。
  • sandboxed command 中途请求 network access。
  • subagent handoff 中包含风险动作。

这些测试覆盖 deterministic gate、classifier context、failure semantics、host surface 四条主路径。

Closing

Claude Code auto mode 的核心设计是把权限系统拆成更细的层,而不是鼓励模型更大胆:

确定性规则处理硬边界
工具级 policy 处理输入语义
fast path 处理高置信低风险动作
classifier 处理上下文语义灰区
host surface 处理人工确认和可观测性
failure semantics 保证不可判断时不偷偷放行

这套结构对 agent / harness 开发者很有参考价值。真正难的部分在于把 classifier 放在正确的位置:它应该有足够上下文判断语义风险,又不能拥有越过规则、sandbox、组织策略和人工确认的权力。Auto mode 的实现给出的答案是:让 classifier 成为权限引擎的一个受限后端,而非 agent 的第二个大脑。