Article cover image

Why your AI agent shouldn't review its own code (and what to do instead)

Author profile image
Aitor Alonso

10 min read

RSS feed

Since I wrote about my agentic software development workflow, I haven’t stopped tweaking it. I’m always looking for ways to improve how I work, and a few weeks after publishing that article I started playing with adversarial reviews. It has become one of my favourite additions to the workflow, so I want to tell you what they are, why they work so well with LLMs, the different flavours you can try, and (just as important) when they aren’t worth it.

I mostly use Claude Code, but everything here applies to Codex or any other agent and harness. Let’s dive in!

What is an adversarial review

An adversarial review is a code review where the reviewer’s job is to find the reasons the change should not ship. It starts from the assumption that the change is broken in some subtle way, and tries to prove it.

OpenAI’s Codex plugin for Claude Code is a great example. The plugin hands the review from Claude Code over to Codex (more on that later), and the prompt Codex receives starts with:

You are Codex performing an adversarial software review.
Your job is to break confidence in the change, not to validate it.

To see the difference, compare it with the requesting-code-review skill from Superpowers that I mentioned in my previous article. It spins up a fresh subagent to review the work, which is great, but the prompt asks it to act as a “Senior Code Reviewer”, to check the work against the plan, and to “acknowledge what was done well before listing issues”. That’s a balanced review, the kind a good teammate would do. Useful, but not adversarial.

So an adversarial review needs two ingredients:

  • Separation. The reviewer is not the author. It doesn’t share the author’s context, conversation, or reasoning.
  • Stance. The reviewer is explicitly told to be skeptical, to assume failure, and to go hunting for it.

With only a fresh context you get a polite second opinion, and with only the stance you get an author pretending to be harsh with their own work, so you need both.

Note
Adversarial doesn’t mean hostile or rude. It means the reviewer plays the opposing role, like the prosecution in a trial or a red team in security. Its role is to attack the change, so the change has to defend itself with evidence.

Why asking the agent to review its own work falls short

The most common way people “review” AI generated code is to ask the same agent, in the same session, something like “now review what you did”. I did it for a while. Occasionally, it would catch some bugs, but this approach has some problems built in.

It’s anchored on its own reasoning. The session contains the plan, every decision, and every “I’ll do it this way because…”. When the agent reads the diff, it reads it through that story, so it reviews the intent instead of the code. It’s the same reason you can’t proofread your own essay well: you read what you meant to write, not what’s on the page.

It wants to agree and to finish. Models trained with human feedback tend to tell you what you want to hear. Anthropic has a paper on sycophancy in language models showing it’s a general behaviour across assistants. Add that the agent’s goal in the loop is to finish the task, and a big finding means more work, and you can guess which way “is this ready?” tends to go.

It likes its own writing. There is research showing that LLM evaluators recognize and favor their own generations, and that the better a model is at recognizing its own output, the stronger that preference is. So even with a fresh context, the same model reviewing code written in its own style starts with a small bias in favour of it.

The model can still be a good reviewer, but a friendly self-review starts tilted towards “looks good to me”.

Why the adversarial setup works

The adversarial setup goes after each of those problems directly.

  • A fresh context judges the code, not the story. The reviewer only gets the diff and the spec (what the change should do). No session history, no justifications. If a decision isn’t obvious from the code, that’s already a finding.
  • The incentive is flipped. In a normal review, success is approving. In an adversarial one, success is finding something that breaks. The Codex prompt even says to return approve “only if you cannot support any substantive adversarial finding”. Now the same eagerness to please pushes the model to dig deeper.
  • A different model has different blind spots. Models from different vendors are trained on different data with different recipes, so their mistakes are less correlated. It’s like asking a senior engineer from another team to look at your PR: they don’t share your team’s assumptions, so they question them.
  • Verification stays mechanical. The reviewer makes claims, and the compiler and the test suite decide who’s right. A finding like “this retry isn’t idempotent” gets settled by writing a failing test. Anthropic summarizes it nicely in their post about large-scale code migrations: “Make review adversarial and verification mechanical.”

Compared with what you’re probably doing today, the difference is where each tool looks. Linters and static analysis catch patterns, but they can’t tell you that your migration isn’t safe to roll back. Same-session self-review is cheap but tilted. And human review is still the final gate, but a human reviewer’s attention is limited, so every issue the adversarial reviewer catches first is one less thing that depends on me noticing it on a Friday afternoon.

Flavours of adversarial review

There isn’t one single way to run an adversarial review. These are the four most common setups I’ve seen, ordered from the cheapest to the most expensive.

1. Same model, fresh context, adversarial prompt

The simplest one. You spin up a subagent (in Claude Code, a custom subagent or a skill that dispatches one) and give it only the diff, the spec, and an adversarial prompt. Something along these lines:

You are reviewing a change you did not write. Your job is to find the strongest
reasons this change should not ship yet. Assume it can fail in subtle ways until
the code proves otherwise.

Spec: <path to the spec>
Diff: run `git diff main...HEAD`

Focus on: data loss, race conditions, retries and idempotency, permissions,
empty or null states, and migrations.

For each finding give: file and lines, what can go wrong, why this code path is
vulnerable, and the likely impact. Report only material findings. If the change
looks safe, say so and return no findings. Do not modify any file.

It’s cheap and easy to wire into your workflow. The downside is the self-preference bias from before: it’s still the same model looking at code in its own style.

2. Cross-model review

Here the reviewer is a model from a different vendor. The easiest way to do it with Claude Code today is OpenAI’s own Codex plugin for Claude Cod e. Once it’s set up, you get a /codex:adversarial-review command. Claude Code collects what to review (your uncommitted changes, or your branch against a base like main), sends it to Codex together with the adversarial prompt, and shows you Codex’s findings verbatim, without Claude rewriting or summarizing them. You can also add some focus text at the end:

/codex:adversarial-review --base main challenge the retry and caching design
Note
For it to work, you must have the Codex CLI installed in your machine with your own Codex account. The plugin’s /codex:setup command checks all of this for you, and can even install Codex if it’s missing.

Its prompt is a great example of how to write an adversarial reviewer. It tells Codex to “default to skepticism”, to “actively try to disprove the change”, and gives it an attack surface to prioritize: auth and trust boundaries, data loss, rollback safety, race conditions, degraded dependencies, schema drift, and observability gaps. The command is also read-only, so Codex returns findings and never touches the code.

It works the other way around too: if you mainly use Codex, have Claude review its work. Just make sure the author and the reviewer are different models.

3. Multiple reviewers and an arbiter

When the change is big, one reviewer isn’t enough. So you run two (or more) adversarial reviewers independently, each in its own context, and when they disagree, a third agent acts as an arbiter and decides which finding holds. The arbiter doesn’t review from scratch, it weighs the evidence each reviewer brings.

This is the setup Anthropic describes in the code migrations post: “Two adversarial reviewers evaluate the work of the implementers using separate contexts and disagreement between reviewers goes to a third agent.” Mike Krieger (co-lead of Anthropic Labs) used it to port a Python codebase to 165,000 lines of TypeScript over a weekend, with “three adversarial review rounds” along the way. For that kind of volume, where no human is going to read every line, this is what keeps the quality up.

4. Debate loop

The most expensive one. The reviewer raises findings, the author answers each of them (fixing it, or rebutting it with evidence), then the reviewer replies, and so on for a few rounds, until they converge or you hit a round limit. There is research backing the idea that multiple models debating improves factuality and reasoning, and some community tools already run Claude and Codex in a debate loop for code review.

It can squeeze out issues the other setups miss, but it burns a lot of tokens and time. Always cap the number of rounds, or the two agents can end up politely agreeing with each other just to end the conversation (sycophancy again, now in both directions).

The gotchas

Adversarial reviews aren’t a free lunch. These are the things that will bite you if you’re not careful:

  • A reviewer told to find problems will find problems. If it can’t find real ones, it will make them up, or flood you with nitpicks. Explicitly allow “no findings” as a valid answer, and ask for quality over quantity. The Codex prompt does both: “Prefer one strong finding over several weak ones” and “If the change looks safe, say so directly and return no findings.”
  • Demand evidence. Every finding needs a file, lines, a concrete failure scenario, and ideally a confidence score. Without that you get plausible-sounding race conditions that don’t exist.
  • Keep the reviewer read-only. The moment the reviewer starts fixing things, it becomes an author, and loses the stance that made it useful. Findings go back to the implementer.
  • Don’t let the author accept everything blindly. The reviewer can be wrong too. The author should verify each finding (a failing test is the best proof) and push back when it doesn’t hold. Superpowers’ receiving-code-review skill is made exactly for this.
  • It costs tokens and time. Every review round is another agent reading the whole change. On a small PR that’s nothing, on a big one it adds up fast, especially with multiple reviewers or debate loops.

When to use it (and when not)

I don’t run an adversarial review on everything. It’s worth it when a subtle bug is expensive:

  • Changes touching auth, permissions, payments, or personal data.
  • Data migrations and anything irreversible, or hard to roll back.
  • Concurrency, retries, queues, and caching, the places where the happy path works and production doesn’t.
  • Long-running autonomous tasks and big migrations, where you won’t read every line yourself.
  • When the agent says “done” suspiciously fast on something that felt hard.

And it’s an overkill for:

  • Typos, copy changes, dependency bumps, and small config tweaks.
  • Spikes and prototypes you’ll throw away anyway.
  • Work without a clear definition of “correct”. If there’s no spec to attack against, the findings end up being opinions.
Warning
An adversarial review does not replace human review. It’s a filter that runs before you look at the code, so your attention goes to the design and the decisions instead of hunting for a missing null check. You still own what gets merged.

Wrapping up

What I value most about adversarial reviews is how they remove the bias from the reviewer. The agent that wrote the code tends to stamp the “looks good to me”, while a separate reviewer told to break the change has no reason to go easy on it, and the tests decide who’s right. At least in theory, that makes it a better reviewer than the agent that wrote the code. So, why don’t you give it a try on your next risky change?

Happy coding!