Skip to content
WorkflowWorkflowsCode reviewCursor

How to Review AI-Generated Code Without Reading Every Line

Model-written code fails differently from human-written code. Reviewing it the same way misses the interesting bugs.

By 5 min read

Workflow: A repeatable sequence you can adopt step by step.

Contents (10 sections)
  1. Review in this order
  2. Six patterns worth hunting for
  3. 1. The parallel implementation
  4. 2. Confidently wrong on one branch
  5. 3. Defensive code for impossible states
  6. 4. Tests that assert the implementation
  7. 5. Comments narrating the code
  8. 6. Silent scope creep
  9. What to stop checking
  10. The two-question version

Reviewing a colleague's pull request, you look for misunderstandings of the requirement, edge cases they may not have considered, and design decisions worth discussing. You give the mechanics — syntax, structure, obvious correctness — a light pass, because a competent person rarely gets those wrong.

Model-written code inverts this. The mechanics are almost always fine. The syntax is clean, the structure is conventional, the naming is reasonable. What is wrong sits somewhere else entirely, and a human-tuned review misses it because it is looking in the wrong place.

Key takeaways

  1. 01Model-written code is strong on syntax and structure and weak on integration, so review integration points first and read the internals last.
  2. 02The highest-value question is 'does this duplicate something we already have?' — that is the most common defect and the easiest to miss.
  3. 03Confidently wrong beats obviously wrong: watch for code that handles the happy path fluently and mishandles one edge case silently.
  4. 04Delete the defensive branches for impossible cases. They are not safety, they are noise that hides real branches.
  5. 05If the diff is too large to review properly, the correct response is to reject the size, not to skim it.

Review in this order#

Reading a diff top to bottom is the wrong strategy, because file order has nothing to do with risk. This order is roughly by defect probability:

1. The boundaries

Every place the new code touches existing code. Function calls into your modules, data passed across a context boundary, anything reading configuration or storage. This is where nearly all real bugs live.

2. Duplication

Search the codebase for the thing it just wrote. Not the name it chose — the behaviour.

3. Error paths

What happens on failure. Happy paths are handled fluently; failure paths are frequently swallowed, logged and ignored, or handled at the wrong level.

4. Dependencies and imports

Anything newly installed or newly imported, including a second date library you already had an answer for.

5. The internals

Last. Loop bodies and pure functions are the part models get right most reliably.

Six patterns worth hunting for#

1. The parallel implementation#

The most common defect by a wide margin. You have formatCurrency in lib/money.ts; the diff contains a new toCurrencyString that does 90% of the same thing and disagrees with the original about negative values.

It passes review easily because in isolation it is good code. The only way to catch it is to search for the behaviour before reading the implementation.

2. Confidently wrong on one branch#

looks right, is wrong
function daysBetween(a: Date, b: Date) {
  const ms = b.getTime() - a.getTime()
  // Correct for most of the year, and off by one across a
  // daylight-saving boundary, which is exactly when someone notices.
  return Math.round(ms / 86_400_000)
}

This is the signature failure mode: fluent, plausible, and wrong in a narrow band that testing on today's date will never surface. Ask of every calculation, "which input makes this wrong?" — and if you cannot construct one within thirty seconds, that is a reason to write a test, not a reason to relax.

3. Defensive code for impossible states#

Null checks on values that cannot be null, try/catch around code that cannot throw, fallbacks for enum cases that do not exist. Harmless individually; corrosive in aggregate, because when every value is treated as possibly-absent, the genuinely optional ones stop standing out.

Delete them. If the type says it is there, trust the type.

4. Tests that assert the implementation#

a test that cannot fail
it("calls the formatter", () => {
  const spy = vi.spyOn(mod, "format")
  render(<Price value={1200} />)
  expect(spy).toHaveBeenCalled()
})

This passes whether or not the price is displayed correctly. Generated tests skew heavily towards asserting that code ran rather than that behaviour is right, and they are worse than no tests because they produce a green suite and a false sense of coverage.

Ask of each test: what user-visible breakage would make this fail? If the answer is "none", delete it.

5. Comments narrating the code#

Comments explaining what the next line does, or why the change is correct. The second kind is the worse one: it is addressed to the reviewer rather than to the next reader, and it becomes misleading the moment the code around it changes.

6. Silent scope creep#

The task was to add a button. The diff also reformats a file, renames a prop, and "improves" an unrelated function. Each change may be defensible; together they make the diff unreviewable, and unreviewable diffs are how regressions ship.

What to stop checking#

Attention is finite, so it is worth naming what you can safely give less of:

  • Formatting. Your formatter handles it. If it does not, install one.
  • Obvious syntax and type errors. The compiler is better at this than you are.
  • Naming conventions in the small. Fix them during integration rather than debating them in review.
  • Whether it could be more elegant. Sometimes worth raising, rarely the best use of the review.

The two-question version#

For a small change, most of this collapses into two questions:

  1. Do we already have this?
  2. Which input makes it wrong?

Those two catch the majority of real defects in generated code. Everything above is the longer form for when the change is large enough to deserve it.

Sources

Primary sources for facts that are not Hamzify testing. Opinions and results from our own work are marked as such in the article.

  1. Modern code review: a case study at Google (Google Research)checked Jul 2026
More from Hamzify

Related reading

Other Hamzify pieces on this topic, the same tools, or the next format worth reading.

Related reading
WorkflowWorkflows

The AI Pair Programming Loop I Actually Use

A repeatable five-step loop for working with an AI coding assistant on a real codebase: brief, constrain, generate, verify, integrate — and what belongs in each step.

Workflows5 min read
Build logBuild Logs

I Gave AI Agents a Real SaaS Build. Here Is Where It Broke.

A full build log of shipping a small SaaS with coding agents doing most of the typing: what the agents handled well, the three places they stalled, and what a human still had to own.

Build Logs6 min read
ResourceResources

A Context Checklist for Briefing Coding Agents

A reference checklist for what an AI coding agent needs to know before it starts: the seven inputs that change output quality, and the ones that only add noise.

Resources4 min read

From the same tool

More Hamzify coverage of Cursor

Reviews, comparisons, builds and workflows that mention Cursor, collected in one place. Open the Cursor coverage.