A Review Agent Should Not Be Able to Write. Here Is How We Enforce It, CLI by CLI.
In a 17 node run, the release agent edited a test to turn a red suite green, then two review agents wrote the same fix and collided. The prompt said review only. It did not hold. Here is the incident, why a written instruction cannot carry that rule, and the exact flags that make Claude Code, Codex, Grok, Antigravity and OpenCode refuse to write.
Earlier this week a user sent us a run report that is worth reading twice. Seventeen agents, one shared worktree, a pipeline with an implementer, a release gate and two review branches. Version 1.171.0 of AgentsRoom.
Three things happened in that run, in this order.
The release agent had a red test suite in front of it. It edited the test spec until the suite was green. In doing so it shipped a real defect, now covered by a test that agreed with it.
Then two review agents, on two parallel branches of the same run, each found a genuine one line bug. Each fixed it directly, in the same worktree, at the same time. They collided.
Every one of those agents had a step prompt that said, in plain words, review only. Nothing stopped them, and nothing signalled it: from the platform's side, a step had write tools and used them.
Why the prompt did not hold
The tempting reading is that the agents ignored the instruction. That is not what happened, and it matters because it changes what the fix has to be.
Each agent had a locally defensible reason to write. A red suite and a spec that looked wrong. A bug that takes four seconds to fix and forty to describe. None of them decided to break the rule. Each decided its case was the one the rule did not mean. From inside the step, the exception always looks reasonable.
A written instruction is a request to the model's judgement. A reviewer that is also able to fix things will, sooner or later, fix things, because fixing is the shortest path from "I found it" to "done". The only rule that survives contact with a plausible exception is one the model cannot argue with: a tool that is not there.
Why the global setting could not do it either
Before this, the only lever that reached a running step was the provider settings, which cover every Claude agent on the machine at once. That is the wrong shape for a run. In the same pipeline, the implementer must write and the reviewer must not. A global switch cannot tell them apart.
And the per agent restriction we already had, the one a ticket can carry when it launches an agent, was deliberately not passed to team steps. So an agent started from a ticket could be restricted, and a node of a team could not. That was the primary cause, and it was a design decision that had aged badly.
The rule: one checkbox on the node
The fix is a boolean on the review node. Tick Read-only and the agent that embodies that step is launched without write access to the project: no file edit, no git commit or push, no shell command whose only job is to change the working tree. Reading, grep, git diff, git log, the tests, the linter and every team tool stay open.
We were explicit about what it is not: it is not a deny list the user writes by hand, per CLI. Nobody should need to know five permission syntaxes to say "this one reviews". The switch produces the right flag for each provider, and it is applied last, after the autonomous mode and after anything the user stored on the agent, so it wins.
What each CLI does, read off its own help
We only enforce on providers whose flag we read on their own --help. A guessed flag kills the launch on a parse error, which is worse than a step with no enforcement. The other CLIs get the written rule only, and the editor says so in plain text under the checkbox.
| CLI | What the read-only switch adds | Holds under autonomous mode? |
|---|---|---|
| Claude Code | --disallowedTools "Edit" "Write" "MultiEdit" "NotebookEdit" "Bash(git commit:*)" "Bash(git push:*)" and the rest | Yes, deny rules apply under --dangerously-skip-permissions |
| Codex | --sandbox read-only | Yes, it is an OS sandbox (Seatbelt on macOS, Landlock on Linux), not a tool list |
| Grok Build | --deny "Edit" --deny "write" --deny "Bash(git commit*)" and the rest | Yes, deny rules apply under --always-approve |
| Antigravity | --mode plan | Yes, plan is the CLI's read-only execution mode |
| OpenCode | --agent plan | Yes, the built-in plan agent denies the edit tools |
| Mistral Vibe, Kimi Code, Copilot, Cursor, Amp, Aider and the rest | prompt paragraph only | No verified flag, and we say so in the editor |
Two details in that table cost us a bug each, so they are worth spelling out.
Codex and Grok refuse a repeated flag. Both parse their arguments with a strict parser. If the user had already stored --sandbox workspace-write on the agent, appending --sandbox read-only would not override it, it would crash the launch. So for valued flags we remove any existing occurrence, with its value, before appending ours. Same for --agent on OpenCode, whose parser turns a repeated flag into an array and fails further down.
On Claude Code the list has to stack. --disallowedTools takes a space separated list and can be repeated, and we already pass one when an agent is not allowed to drive the embedded browser. The parser concatenates a repeated variadic option, so the two lists add up instead of the second replacing the first.
The full Claude Code list is the four file editing tools, every git subcommand that writes to the index, the tree, the refs or a remote (add, commit, push, merge, rebase, reset, checkout, switch, restore, stash, cherry-pick, revert, apply, am, rm, mv, clean, tag, worktree), and the shell commands that only exist to change files (rm, mv, cp, tee, touch, mkdir, chmod, chown, ln, truncate, dd, sed -i). Grok takes the same rule strings, in its glob form, plus its own names for the file tools (search_replace, write, hashline_edit).
The prompt still has a job
The flag refuses. It does not explain. And an agent that hits a refusal it does not understand treats it as a bug and looks for another way through, which is the exact behaviour we were trying to remove.
So a read-only step also gets two sentences in its prompt. The first says the step is read-only, lists what that means, and states that a refusal is the rule, not an obstacle to route around with another command. The second lists what stays open, and tells the agent to report what should change, with the file, the line and the reason, in its handoff, and let the step that owns the code apply it.
On the CLIs with a verified flag, that paragraph is what makes the refusal understood. On the others, it is the whole enforcement, and we would rather say that than pretend.
Who is read-only in the shipped templates
The nodes that judge are read-only: the QA verification step of the two starter templates, the reproduction and verification steps of Bug hunt, the QA and Security branches of Release shield, the tester of Feature squad.
The nodes that own the code keep writing: the developer, and the release gate that is written to fix every finding itself. A release gate that cannot write is a release gate that cannot release.
That split is the whole design, and it is the split the incident violated twice: a release node that wrote in the wrong place, and review nodes that wrote at all.
What this is not
It is not a security boundary. The reporter said it in the report, and they were right: a bash -c gets around a tool deny list. If you need to isolate an agent you do not trust, that is a sandbox or a separate machine, and Codex is the only one of the five whose read-only mode is actually that.
What the switch stops is the accident and the role drift, which is what actually happens. A reviewer does not escape a deny list on purpose. It reaches for Edit by reflex, and the reflex is now refused.
What we did not build
The reporter asked for one more thing: an event in the run timeline saying "node X wrote to the tree", as the minimum signal even without enforcement. It is a good idea and we did not do it here, because it needs a baseline diff per step on the runner side. If the need comes back, that is the next piece.
If you are not using AgentsRoom
The flags above are copyable as they are. A review agent launched by hand with codex --sandbox read-only, or claude --disallowedTools "Edit" "Write" "MultiEdit" "NotebookEdit" "Bash(git commit:*)" "Bash(git push:*)", cannot do what our two review nodes did. Put the same two sentences in its prompt so it knows why it is being refused.
What the checkbox adds is that you do not have to remember which of the five syntaxes applies, that the flag wins over whatever autonomous mode the step runs in, and that it survives a run that re-enters the same step later.
The node switch and the per provider table are documented on the Agent Teams page. Whether a review by an agent is worth having at all, and how much of a diff still deserves a human, is a different question, and we wrote about it in Should you still review your AI agent's code?. This post is about the smaller, more mechanical thing: once you have decided an agent reviews, make it physically unable to do anything else.
Download AgentsRoom
Run all your AI agents, on all your projects, from a single window.
Companion app: monitor your agents on the go
Bring your own: Claude, Codex, Antigravity CLI, or other AI provider.
Push bugs and requests straight to your public backlog.
Keep reading
AI Agent Loops: How Self-Correcting Coding Agents Finish the Job
An AI agent loop turns prompt-and-fix into a self-correcting cycle: the agent writes a plan, builds it, reviews its own work against the plan, and loops until it is done. How the loop works in Claude Code, Codex, Antigravity CLI, Cursor and the Ralph loop.
Read the articleTen Agents Ran the Same Type Check at Once. The Fix Was a Directory.
Seventeen coding agents in one checkout, ten tsc processes at the same time, load average 37 and 87 MB of free RAM. A ninety second type check took 7 minutes 36. Here is the measurement, why the machine was not computing, and the small shared lock that fixed it. Copyable into any repository.
Read the articleMy AI Running Coach Is a Git Repo and a Claude Agent
I finish a run, my watch syncs, and three minutes later the analysis is written in my repository, the week has been adjusted, and my coach has left a comment under the Strava activity. No app built, no server written, no per-token API bill: a Claude subscription, AgentsRoom, and Markdown files. Here is the whole build, reproducible.
Read the article