审批策略:同一条命令,问不问看哪两颗旋钮
默认问不问跟沙箱种类绑在一起。问过一次之后,记住的是完整命令,还是一段前缀。
本页解决的问题
先给结论「审批策略:同一条命令,问不问看哪两颗旋钮」要解决的关键问题是什么?
默认问不问跟沙箱种类绑在一起。问过一次之后,记住的是完整命令,还是一段前缀。
跟着交接处走,不要只看 Demo。 系统是否可靠,往往取决于模型、工具、状态、权限和人的交接处。把每次交接都当成可以观察、测试和恢复的地方。
为一个自动化步骤写清输入、负责人、审批和恢复动作。
一次运行成功了,却说不清发生了什么,也无法安全重放。
- 读当前 turn 的 AskForApprovalprotocol.rs L924
- 看 FileSystemSandboxKind 是不是 Restrictedpermissions.rs L227
- Never 给 Skip,UnlessTrusted 给 NeedsApprovalsandboxing.rs L198
- OnRequest 或 Granular 只在 Restricted 时要问sandboxing.rs L200
- Granular 要问且关掉沙箱审批则 Forbiddensandboxing.rs L209
- execpolicy 的 Prompt 再过第二道闸exec_policy.rs L214
- 会话缓存只收 ApprovedForSession,认精确 keysandboxing.rs L108
- 策略改了,给模型的说明书一起改permissions_instructions.rs L271
你把审批留在 on-request,沙箱从 workspace-write 拧到 danger-full-access。十分钟前那条出沙箱命令还会弹窗。现在不弹了。你没改审批旋钮。
审批策略 AskForApproval 是当前 turn 用哪套问人规则,类型里有四个变体。文件系统沙箱种类是三个,默认 Restricted,意思是只读或只能写工作区。这两颗旋钮在同一个判定函数里。改其中一颗,另一颗的行为会跟着走。出处:codex-rs/protocol/src/protocol.rs 第 924 至 947 行,以及 codex-rs/protocol/src/permissions.rs 第 227 至 232 行
一条命令先读审批策略,再看文件系统是不是 Restricted,再让 execpolicy 把 Allow、Prompt、Forbidden 叠上去。execpolicy 是按命令文本给出三态的规则文件。
默认问不问写在 default_exec_approval_requirement。Never 这一层给 Skip。UnlessTrusted 这一层给 NeedsApproval。OnRequest 和 Granular 只在 Restricted 时要问。read-only 和 workspace-write 都是 Restricted,所以这两格的默认结局一样。danger-full-access 把 kind 变成 Unrestricted,默认函数走 Skip。出处:codex-rs/core/src/tools/sandboxing.rs 第 198 至 230 行
Granular 还多一个闸。需要审批、且 sandbox_approval 关掉时,直接 Forbidden。文件系统如果已经是 Unrestricted,needs_approval 先变成假,Forbidden 分支进不去。关掉沙箱审批,只挡住本来就要弹的窗。
Never 遇到 execpolicy 的 Prompt,提问会升级成拒绝。它并不表示什么都准跑。出处:codex-rs/core/src/exec_policy.rs 第 214 至 236 行
untrusted 写不进配置。类型还在,公开字符串已经退休。用户显式写出,整次加载失败。没写才看项目信任:已信任走 OnRequest,明确未信任走 UnlessTrusted。出处:codex-rs/core/src/config/mod.rs 第 3607 至 3625 行
还有一个加载期硬拒绝。requirements 不允许 danger-full-access,配置却写了 approval_policy = "never",加载器会先把权限档案回落到只读。只读加上从不提问,等于模型在窄沙箱里还没人可问。这种组合直接判非法。出处:codex-rs/core/src/config/mod.rs 第 3969 至 3979 行
全盘可写时,再问一次出沙箱没有意义。两颗旋钮绑在一起,少弹窗。代价是改沙箱会静默带走审批行为。换个语言重写,仍要先回答:全权模式还要不要问人。
策略枚举靠穷尽分支表达规则。新加一个变体,编译器会逼所有判定函数表态。这是类型在替运行时守门。
弹窗上两条记住长得很像。一条是本会话记住这条命令。一条是把前缀写进 execpolicy,跨会话生效。点错了,后面的边界题就会答反。
常规 exec 弹窗默认菜单里,甚至没有 ApprovedForSession。有网络上下文时才带上它。普通命令给一次批准,有前缀修正提案时再加按前缀记住,最后是取消。用户最常碰到的记住,是前缀那一条。出处:codex-rs/protocol/src/approvals.rs 第 314 至 347 行
会话缓存活在 ApprovalStore 里,跟会话同寿命。key 是规范化后的完整命令,外加 cwd、环境和 execpolicy 指纹。所有 key 都已经是 ApprovedForSession 才命中。Approved 一次放行不会进 map。出处:codex-rs/core/src/tools/sandboxing.rs 第 64 至 116 行
npm run test 批准进会话,npm run lint 是另一把 key。/bin/bash -lc 和 bash -lc 在能拆出单一明文命令时,缓存成同一组 token。会话缓存认完整命令,不认 npm *。前缀扩张只走 execpolicy。
改审批策略不会清空这座 map。key 里没有 AskForApproval。中途从 OnRequest 改成 UnlessTrusted,已经缓存的批准仍算数。改 execpolicy 会改指纹,缓存才会失效。
两套记住对应两种威胁。精确 key 挡不住换参数。前缀挡得住换参数,也容易把包装命令的范围扩太大。两层分开,才能各自配禁建议名单。一天弹窗不多的时候,先只做一次放行也成立。
策略改了,只改运行时分支不够。模型看到的说明必须同步改,否则它会按旧规则去要 require_escalated。Never 下面如果说明书还在教它提权,运行时会把提问升级成拒绝,模型只会反复撞墙。
权限说明是一段 developer 消息。先选沙箱模板,再选审批模板。Never、UnlessTrusted、OnRequest 各有现成 markdown。Never 那份只有一句:不要再给 sandbox_permissions,命令会被拒。Granular 没有第五份文件,按五个开关现场拼允许列表和拒绝列表。两套说明拼在同一段里,模型一次看到当前组合。出处:codex-rs/prompts/src/permissions_instructions.rs 第 271 至 291 行
钩子在人前面。钩子给出 Allow 或 Deny,弹窗就不会出现。钩子的 Allow 是一次 Approved,不写会话缓存。
运行时和模型说明书是同一份合同的两面。判定函数改了,字典那一行必须一起改。把这两份放在同一个模块,用同一组测试喂两边,换语言也用得上。
DSH:两颗旋钮,一次授权不扩大
DSH 的审批策略只有 ask 和 never。never 在分派给回答者之前就返回 rejected,后挂的监听器改不了这个承诺。授权结果只有 allowed-once。没有本会话缓存,没有前缀修正。问过一次,下一次还问。出处:packages/interaction/user-approval/src/index.ts 第 84 至 94 行,以及第 304 至 312 行
沙箱和审批是两颗独立旋钮。用户看见的下拉框是预设表:workspace-write 绑 ask,danger-full-access 绑 never。点下一档时分别调用两颗 setter。对不上表就显示 custom。所以 DSH 可以单独把审批留在 ask、把沙箱拧到全盘可写。Codex 的 OnRequest 在 Unrestricted 上默认 Skip,这个组合在默认函数里不可独立存在。出处:packages/interaction/permission-presets/src/index.ts 第 167 至 176 行
Claude Code:记住写进规则表
Claude Code 对外的权限模式是五档,外加内部的 auto 和 bubble。规则来源包括 userSettings、projectSettings、session、cliArg。判定先查整工具级 deny,再往下走。出处:restored-src/src/types/permissions.ts 第 16 至 29 行,以及 restored-src/src/utils/permissions/permissions.ts 第 1169 至 1181 行
alwaysAllowRules 可以按来源记下允许项,session 是其中一档。下次按规则匹配,不必完整 argv 相等。代价是匹配函数必须自己防包装。Codex 把扩大范围交给 execpolicy 前缀和禁建议名单。dontAsk 接近 Never。Codex 没有公开的全放行审批策略,Never 仍会被 execpolicy 拦住。出处:restored-src/src/types/permissions.ts 第 54 至 62 行,以及第 433 行
拧沙箱,弹窗还在吗
审批留在 on-request,沙箱从 workspace-write 拧到 danger-full-access。再跑一条会写工作区外路径的命令。弹窗还在不在?为什么?
接着把同一条 npm run test 按会话批准,再提交 npm run lint。缓存该不该命中?如果点的是按前缀记住,答案会不会变?
「先玩一遍 · 同一条命令,换策略看结局」的能力藏在每次交接里
「你把审批留在 on-request ,沙箱从 workspace-write 拧到 danger-full-access 。十分钟前那条出沙箱命令还会弹窗。现在不弹了。你没改审批旋钮」说明,Agent 的表现不只由模型决定。模型、上下文、工具、状态、权限和人之间的每次交接,都会改变任务能否继续以及出了问题能否恢复。
先写清状态,再增加能力
从「审批策略 AskForApproval 是当前 turn 用哪套问人规则,类型里有四个变体。文件系统沙箱种类是三个,默认 Restricted ,意思是只读或只能写工作区。这两颗旋钮在同一个判定函数里。改其中一颗,另一颗的行为会跟着走。」出发,可以把流程拆成起始状态、下一步动作、工具返回、状态更新和停止条件。这样调试时找的是第一处丢失信息或权限的位置,而不是笼统地说“模型变笨了”。
- 读当前 turn 的 AskForApproval protocol.rs L924
- 看 FileSystemSandboxKind 是不是 Restricted permissions.rs L227
- Never 给 Skip,UnlessTrusted 给 NeedsApproval sandboxing.rs L198
成功路径不能代表系统可靠
用「接着把同一条 npm run test 按会话批准,再提交 npm run lint 。缓存该不该命中?如果点的是按前缀记住,答案会不会变」重放一次成功和一次失败,记录每一轮真正传入的上下文、工具结果和负责人;只要第二个人能复述这条链,系统才有可维护性。
从「先玩一遍 · 同一条命令,换策略看结局」走到「思路一 · 默认问不问,跟沙箱种类绑在一起」
「先玩一遍 · 同一条命令,换策略看结局」先把问题落在「四种策略,三种沙箱,看哪一格盖放行、问人、拒绝 播放 单步 重置 命令 git status npm run test npm run lint prompt 规则 前三条走 Allow。第四条命中 execpolicy 的 Prompt。 策略 OnRequest UnlessTrusted Granular Never 沙箱 read-only workspace-write danger-full-access…」上;到了「思路一 · 默认问不问,跟沙箱种类绑在一起」,讨论继续推进到「你把审批留在 on-request ,沙箱从 workspace-write 拧到 danger-full-access 。十分钟前那条出沙箱命令还会弹窗。现在不弹了。你没改审批旋钮」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。
把这条判断带到下一个场景
分析 Agent 时,沿着状态、动作、工具结果和下一步的顺序走一遍;每次交接都要能说明信息从哪里来、由谁确认、失败时停在哪里。
- 「先玩一遍 · 同一条命令,换策略看结局」:四种策略,三种沙箱,看哪一格盖放行、问人、拒绝 播放 单步 重置 命令 git status npm run test npm run lint prompt 规则 前三条走 Allow。第四条命中 execpolicy 的 Prompt。 策略 OnRequest UnlessTrusted Granular Never 沙箱 read-only workspace-write danger-full-access…
- 「思路一 · 默认问不问,跟沙箱种类绑在一起」:你把审批留在 on-request ,沙箱从 workspace-write 拧到 danger-full-access 。十分钟前那条出沙箱命令还会弹窗。现在不弹了。你没改审批旋钮
- 「最后的要点」:Granular 要问且关掉沙箱审批则 Forbidden sandboxing.rs L209
最后的「最后的要点」把讨论落到「Granular 要问且关掉沙箱审批则 Forbidden sandboxing.rs L209」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。
我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。
读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。
如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。
还没有这篇文章的讨论。