审查代理不应该能写代码。我们如何逐个 CLI 强制这一点。
在一次 17 个节点的运行中,发布代理为了让红色的测试套件变绿而改掉了测试,随后两个审查代理写下了同一个修复并撞在一起。提示词写的是只做审查。它没有守住。这篇文章讲这次事故、为什么书面指令承担不了这条规则,以及让 Claude Code、Codex、Grok、Antigravity 和 OpenCode 拒绝写入的确切标志。
本周早些时候,一位用户给我们发来一份值得读两遍的运行报告。十七个代理,一个共享的 worktree,一条包含实现者、发布门禁和两条审查分支的流水线。AgentsRoom 1.171.0 版本。
那次运行里发生了三件事,按这个顺序。
发布代理面前是一个红色的测试套件。它把测试 spec 改到套件变绿为止。这样一来,它把一个真实的缺陷发了出去,而且现在有一个认同这个缺陷的测试给它背书。
接着,两个审查代理在同一次运行的两条并行分支上,各自发现了一个真实的一行 bug。各自直接修掉,在同一个 worktree 里,在同一时刻。它们撞在了一起。
这些代理每一个的步骤提示词都用大白话写着:只做审查。没有任何东西阻止它们,也没有任何东西发出信号:从平台这边看,一个步骤拥有写入工具,然后用了。
为什么提示词没有守住
最诱人的解读是代理无视了指令。事情不是这样的,而这一点很重要,因为它决定了修复必须是什么样。
每个代理都有一个局部说得通的写入理由。一个红色的套件加一个看起来写错了的 spec。一个修掉要四秒、描述要四十秒的 bug。没有一个决定违反规则。每一个都认定自己的情况是规则没打算管的那种。从步骤内部看,例外总是显得合理。
书面指令是向模型的判断力提出的一个请求。一个同时能修东西的审查者,迟早会去修,因为修是从「我找到了」到「搞定」最短的路。唯一能在一个貌似合理的例外面前活下来的规则,是模型没法争辩的那种:一个根本不存在的工具。
为什么全局设置也做不到
在此之前,唯一能触及正在运行的步骤的杠杆是提供商设置,而它一次覆盖这台机器上所有的 Claude 代理。对一次运行来说,这个形状不对。在同一条流水线里,实现者必须写,审查者不能写。一个全局开关分不清它们。
而我们已经有的按代理的限制,也就是工单启动代理时可以带上的那个,被刻意没有传给团队步骤。所以从工单启动的代理可以被限制,团队里的节点却不行。这是主因,也是一个已经过时的设计决定。
规则:节点上的一个复选框
修复是审查节点上的一个布尔值。勾上只读,承担这个步骤的代理启动时就没有项目的写入权限:不能编辑文件,不能 git commit 或 push,不能运行那些唯一作用就是改动工作树的 shell 命令。读取、grep、git diff、git log、测试、linter 和所有团队工具保持开放。
我们明确说了它不是什么:它不是一份由用户逐个 CLI 手写的拒绝列表。没有人应该为了说一句「这个只做审查」而去掌握五种权限语法。这个开关为每个提供商生成正确的标志,并且最后应用,排在自主模式之后,排在用户保存在代理上的任何东西之后,所以它赢。
每个 CLI 做什么,从它们各自的帮助里读出来
我们只在那些标志是从它们自己的 --help 里读出来的提供商上强制。一个猜出来的标志会因解析错误而让启动失败,这比一个没有强制的步骤更糟。其他 CLI 只拿到书面规则,编辑器会在复选框下面用明文说明。
| CLI | 只读开关添加了什么 | 在自主模式下还有效吗? |
|---|---|---|
| Claude Code | --disallowedTools "Edit" "Write" "MultiEdit" "NotebookEdit" "Bash(git commit:*)" "Bash(git push:*)" 等 | 有效,拒绝规则在 --dangerously-skip-permissions 下依然生效 |
| Codex | --sandbox read-only | 有效,这是操作系统沙箱(macOS 上是 Seatbelt,Linux 上是 Landlock),不是工具列表 |
| Grok Build | --deny "Edit" --deny "write" --deny "Bash(git commit*)" 等 | 有效,拒绝规则在 --always-approve 下依然生效 |
| Antigravity | --mode plan | 有效,plan 是这个 CLI 的只读执行模式 |
| OpenCode | --agent plan | 有效,内置的 plan 代理拒绝编辑工具 |
| Mistral Vibe、Kimi Code、Copilot、Cursor、Amp、Aider 及其他 | 仅提示词段落 | 没有经过验证的标志,我们在编辑器里也这么说 |
这张表里有两个细节各让我们付出了一个 bug 的代价,值得说清楚。
Codex 和 Grok 拒绝重复的标志。 两者都用严格的解析器处理参数。如果用户已经在代理上保存了 --sandbox workspace-write,再追加 --sandbox read-only 不会覆盖它,而是让启动崩溃。所以对于带值的标志,我们会先移除已有的出现,连同它的值,再追加我们的。OpenCode 的 --agent 也一样,它的解析器会把重复的标志变成一个数组,然后在更下游失败。
在 Claude Code 上,列表必须叠加。 --disallowedTools 接受一个以空格分隔的列表并且可以重复,而当一个代理不允许操作内嵌浏览器时,我们已经传了一个。解析器会把重复的可变参数选项拼接起来,所以两个列表相加,而不是第二个替换第一个。
Claude Code 的完整列表是:四个文件编辑工具,每一个会写入索引、工作树、引用或远程仓库的 git 子命令(add、commit、push、merge、rebase、reset、checkout、switch、restore、stash、cherry-pick、revert、apply、am、rm、mv、clean、tag、worktree),以及那些存在的唯一目的就是改动文件的 shell 命令(rm、mv、cp、tee、touch、mkdir、chmod、chown、ln、truncate、dd、sed -i)。Grok 接受同样的规则字符串,用它的 glob 形式,再加上它自己给文件工具起的名字(search_replace、write、hashline_edit)。
提示词仍然有活要干
标志负责拒绝。它不解释。而一个撞上自己不理解的拒绝的代理,会把它当成一个 bug,然后另找一条路走通,这恰恰是我们想去掉的行为。
所以一个只读步骤的提示词里还会多两句话。第一句说明这个步骤是只读的,列出这意味着什么,并声明拒绝就是规则本身,不是一个要换条命令绕过去的障碍。第二句列出仍然开放的东西,并告诉代理在交接里报告应该改什么,附上文件、行号和原因,让拥有代码的那个步骤去应用。
在有经过验证的标志的 CLI 上,这段话是让拒绝被理解的关键。在其他 CLI 上,它就是全部的强制手段,我们宁愿把这话说出来,也不假装。
内置模板里谁是只读的
那些做判断的节点是只读的:两个入门模板的 QA 验证步骤,Bug hunt 的复现和验证步骤,Release shield 的 QA 和安全分支,Feature squad 的测试者。
那些拥有代码的节点继续写:开发者,以及被设定为自己修掉每一个发现的发布门禁。一个不能写的发布门禁,就是一个不能发布的发布门禁。
这个分工就是整个设计,也是这次事故两度违反的分工:一个在错误的地方写了的发布节点,和几个压根就不该写的审查节点。
这不是什么
它不是安全边界。报告者在报告里说了,而且说得对:一个 bash -c 就能绕过工具拒绝列表。如果你需要隔离一个你不信任的代理,那是沙箱或一台单独机器的事,而五个 CLI 里只有 Codex 的只读模式真的是这个东西。
这个开关拦住的是意外和角色漂移,而这才是实际会发生的事。审查者不会故意逃出拒绝列表。它是出于本能去抓 Edit,而这个本能现在会被拒绝。
我们没有做的
报告者还提了一件事:在运行时间线里加一个事件,写着「节点 X 写入了工作树」,作为即使没有强制也该有的最低限度的信号。这是个好主意,我们这次没做,因为它需要在运行器那边为每个步骤留一份基线 diff。如果这个需求再回来,那就是下一块。
如果你不用 AgentsRoom
上面的标志可以原样复制。一个用 codex --sandbox read-only 或 claude --disallowedTools "Edit" "Write" "MultiEdit" "NotebookEdit" "Bash(git commit:*)" "Bash(git push:*)" 手动启动的审查代理,做不了我们那两个审查节点做的事。把同样的两句话放进它的提示词,让它知道自己为什么被拒绝。
复选框额外带来的是:你不必记住五种语法里哪一种适用,标志压过步骤运行时所处的任何自主模式,而且在稍后重新进入同一步骤的运行里依然有效。
节点开关和按提供商列出的表格记录在 Agent Teams 页面上。代理做的审查到底值不值得有,一个 diff 里还有多少部分值得人来看,是另一个问题,我们在你还应该审查你的 AI 代理的代码吗?里写过。这篇文章讲的是更小、更机械的那件事:一旦你决定让一个代理做审查,就让它在物理上做不了别的任何事。
下载 AgentsRoom
在一个窗口中运行你所有项目的所有 AI 代理。
配套应用:随时随地监控你的 Agent
使用 Claude、Codex、Antigravity CLI 或其他 AI 提供商。
把 Bug 和需求直接发送到您的公开待办清单。
继续阅读
AI 代理循环:自我纠错的编码代理如何把活干完
AI 代理循环把「提示加修补」变成一个自我纠错的闭环:代理先写计划,再动手实现,然后对照计划检查自己的成果,循环往复直到完成。看看这个循环在 Claude Code、Codex、Antigravity CLI、Cursor 和 Ralph loop 中是怎么运转的。
阅读全文Codex、Claude Code、Cursor 还是 Copilot:按工作方式来选
对比 Codex、Claude Code、Cursor、GitHub Copilot 和 AgentsRoom,不看功能清单,而是按工作方式来选:一个人写代码、同时跑多个 agent,还是和团队一起交付。
阅读全文10个代理同时跑了同一个类型检查。解决办法是一个目录。
同一个检出目录里17个编码代理,同时跑着10个tsc,load average 37,空闲内存只剩87 MB。一次九十秒的类型检查花了7分36。这里是我们的测量数据,机器为什么根本没在计算,以及那个解决了问题的小小共享锁。可以直接抄进任何仓库。
阅读全文