Review AI-generated code in a fixed order: data shape first, then boundaries — inputs, outputs, and error paths — then logic last. Reading top-to-bottom fails because generated code is usually locally plausible, meaning each individual line looks reasonable in isolation, while being globally questionable, meaning the pieces don't actually cohere. A linear read is built to catch the first kind of problem and is structurally blind to the second.

Updated August 2026. This order holds regardless of which agent wrote the code. Claude Code, Cursor and Copilot fail in similar shapes, because none of them can watch their own code run and notice it doesn't do what its docstring claims. That check is still yours, and it's part of the wider picture of how to review what your AI coding agent built — the one thing only a human reviewer currently supplies.

Why is generated code harder to read?

Generated code is harder to read because it's optimized to look finished, not to be understood. A human writing new code builds structure around a problem held in their head; a model predicts the next plausible token. The result can be syntactically clean and locally correct while skipping the reasoning trail a reviewer normally relies on to trust it.

When a teammate hands you a pull request, you can usually reconstruct why they made a choice — ask them, check the commit history, or recognize the pattern from three other files in the codebase. A model's stated "reasoning," when you can see it at all, is a plan it wrote before touching a file, not a record of tradeoffs made mid-implementation. It also can't watch its own code run — the same blind spot that keeps it from seeing what it built visually shows up here too: it reasons about code in the abstract and never observes what actually happens when it executes.

That's why "this reads fine" is weaker evidence than usual with generated code. Reading fine and running correctly are different claims, and generated code has a habit of collapsing the two in your head before you've checked either one.

Where should you start?

Start with data shape: the structures moving through the code, not what the code does to them. Read type definitions, function signatures, schemas and API contracts before any logic. If the shape is wrong — an array where the caller expects an object, a field assumed required that's actually optional — every function built on it is wrong too.

This is also the fastest part of the review, which is exactly why it goes first:

  1. Data shape (2–5 minutes) — types, schemas, function signatures, request and response contracts. You're checking that what the code assumes about its data matches reality.
  2. Boundaries (10–15 minutes) — every place data enters or leaves: inputs, outputs, error paths, external calls. This is where most real bugs live.
  3. Logic (whatever's left) — the algorithm itself, read last, once you already trust the shape and the edges it operates on.

Reading logic first is tempting, because it's the most interesting part of any diff. But logic review only tells you the code does what it appears to do. It can't tell you whether what it appears to do matches what the data actually looks like once it's running.

What deserves the most scrutiny?

Boundaries deserve the most scrutiny: anywhere data crosses a trust line, or an error can occur. Input validation, API responses, database writes and catch blocks are where generated code most often looks right and behaves wrong, because the model is guessing at failure behavior it has no real feedback loop for.

A function's happy path is the easiest thing for a model to get right, because it's also the most common pattern in its training data. What's scarce in that data is the unhappy path specific to your system: what your API actually returns on a 429, what your database does with a duplicate key, what your frontend should show when a fetch times out mid-render. Generated code tends to fill those gaps with something plausible-sounding rather than something correct for your system — which is exactly why it reads clean and behaves wrong.

What can you safely skim?

Skim self-contained pure logic — a sort comparator, a formatting helper, a small math utility — anything with no side effects and a bounded input space you can eyeball or test in seconds. If a function only transforms values already validated at a boundary, and its tests pass, your attention is worth more spent elsewhere in the diff.

The test isn't "is this function simple," it's "can this function's mistakes hide." A one-line date formatter can only be wrong in ways you'll notice immediately. A function that reshapes an API response before it reaches five other files can be wrong in ways that surface three screens later, in code that looks unrelated to the actual cause. Spend your limited attention on the second kind, and let the first kind's own simplicity do the checking for you.

What are the specific AI failure patterns?

Five patterns recur across agents: invented APIs that don't exist, errors caught and silently discarded, logic duplicated instead of reused, defensive null checks that hide a real bug instead of surfacing it, and confident comments describing behavior the code doesn't actually have. Each leaves a specific, greppable signature you can search for directly.

Failure pattern What it looks like What to grep for
Invented APIs A method, package or config flag that doesn't exist in the library actually installed The exact import or method name against package.json / requirements.txt and the library's real type defs — not the model's memory of them
Silently swallowed errors A catch that logs and moves on, or catches nothing at all catch (e) {}, catch (e) { console.log, except Exception: with no re-raise, .catch(() => {})
Duplicated logic The same validation, parsing or formatting rewritten instead of reusing what already exists A distinctive literal — a regex, an error string, a magic number — that should appear once but shows up twice
Over-defensive null checks ?., ?? [], || {} stacked on a value that should never be null if the code above it is correct Optional-chaining density per file, and any ?. sitting two lines below a check that already confirmed the value exists
Confident wrong comments A comment claims a behavior — retries, caching, validation — that the code below it doesn't implement Comments containing "retr", "cache", "valid", "sanitiz" — then read the code under each one and confirm it actually does that

Invented APIs are the pattern worth taking most seriously, because it isn't a rare edge case. A 2025 study of 16 code-generating LLMs across 576,000 code samples found that 19.7% of the packages they recommended were hallucinated — names that don't exist in the registry the code claims to import from (Source: USENIX Security 2025). That's not a model being sloppy once in a while; it's close to one in five package references worth a second look.

Duplicated logic is the pattern most likely to slip past a diff review, because a duplicate function usually compiles, passes its own tests, and looks locally fine. The cost only shows up later, when someone fixes a bug in one copy and not the other.

How deep is deep enough?

Deep enough means every boundary touching money, auth or data you can't recreate has been traced to a real input or output, not just read. Reading generates a hypothesis about what the code does; running it against a real case confirms or kills that hypothesis. Everything lower-risk can stop at a confident read.

This is the verify step of the plan → execute → verify loop, and it's worth treating as genuinely separate from reading, not a formality tacked on after it. Anthropic's own best-practices guidance for Claude Code makes a related point: a session that just wrote the code is a biased reviewer of it, which is part of why their documentation recommends a fresh subagent, working in a clean context, specifically for a second look (Source: Anthropic, 2026). The same reasoning applies to you reading your own agent's diff right after watching it write the thing.

Use risk, not diff size, to set the depth. A 40-line change to a currency-conversion function deserves a full boundary trace even if it looks trivial; a 400-line change to a purely internal logging helper might not. Pair this with the "The agent said it's done": 12-point checklist for verifying AI-built UI whenever the change also has a visual surface — code review and UI verification are different checks, and generated code regularly passes one while failing the other.

What to do next

Open type definitions and function signatures first. Trace every boundary — inputs, outputs, catch blocks — against one real case before trusting it. Read logic last, and treat that pass as confirmation rather than discovery. If the actual bug is visual rather than structural, a code review won't find it, because nothing in the code itself is wrong.

That last case is worth naming directly: the code compiles, the types check out, the boundaries hold — and the output still looks wrong. A code review can't catch that, because there's genuinely nothing wrong with the code. That's a different problem with a different fix, the one covered across how to describe a visual bug to an AI coding agent and how to give feedback to Claude Code so the fix actually lands. When what's broken is what something looks like rather than how it's built, showing your agent what you saw does more than another paragraph of description ever will.