Reviewing agent output is not the same as reviewing a colleague's PR, because the failure distribution is different.
A colleague makes mistakes near the edges of what they know. An agent makes mistakes near the edges of what it was told. That means the bugs cluster somewhere unfamiliar: not in the tricky algorithm, but in the boundary conditions nobody specified.
Read in this order.
Pass 1 — What did it decide that I did not?#
Before you read a single line for correctness, scan for decisions.
- It chose a default. Which one, and is it right?
- It picked an error type. Does that match the rest of the codebase?
- It handled the empty case. How?
- It named something. Does that name mean what it says?
Every one of these is a place where ambiguity in your request got resolved without discussion. This pass takes ninety seconds and finds the most expensive class of problem, because these are the bugs that pass tests.
A quick way to run it: ask the agent directly.
List every decision you made that my request did not specify, and what you chose.
The list is usually longer than you expected and about a third of it is worth arguing with.
Pass 2 — Does the test actually test it?#
Agent-written tests have a characteristic failure: they assert what the implementation does rather than what the requirement is. The test passes, it will always pass, and it protects nothing.
Look for:
- Tests that mirror the implementation. If the test computes the expected value using the same logic as the code, it tests nothing.
- Assertions on shape, not behaviour.
assert(result != nil)is not a test. - No failure case. If every test is a happy path, the error handling is unverified.
- Mocks that assume the answer. A mock returning exactly what the code expects proves the code handles that one case.
The fastest check: break the implementation on purpose and confirm the test fails. If it does not, the test is decorative.
Pass 3 — What is at the boundary?#
This is where the real bugs are. Not in the middle of the function — at its edges.
- Empty input, single-element input, very large input
- Concurrent calls to something that looks single-threaded
- The error path: does it leave a transaction open, a lock held, a file handle leaked?
- Time: timezone assumptions, clock skew, "now" captured once versus repeatedly
- Money and counts: rounding, integer division, off-by-one in pagination
Agents write the middle of a function well. They write the edges from a general pattern, and general patterns do not know that your pagination is one-indexed.
Pass 4 — Is anything here that should not be?#
Last, scan for scope creep.
- Files touched that you did not mention
- Reformatting mixed with logic changes
- A dependency added
- A "small improvement" to adjacent code
- Comments explaining what the line already says
None of these are bugs. All of them make the next review harder, and they compound. A codebase where every change carries 20% unrelated churn becomes unreviewable within a few months.
Send it back. It costs one sentence — revert everything outside the two files I named — and it is the difference between a codebase you can still reason about in a year and one you cannot.
What not to do#
Do not ask the agent whether its own code is correct. It will say yes, thoroughly and with examples. Self-review is the weakest signal available; it catches syntax-level problems you already caught and misses the assumption error entirely.
What does work is asking a fresh context to review it, with the original requirement and without the implementation history. That is a genuinely different reader, and it finds things.
Do not skip the read because the tests pass. The tests were written by the same process that wrote the bug.
The habit that matters most#
Read the diff before you run the code.
It is tempting to try it, see it work, and skim the changes. But "it works" only tells you it works for the path you just exercised, and the entire risk of agent-written code lives in the paths you did not.
Next: making an agent reproduce a bug before it explains one.