Reviewing your own AI-assisted change is one problem. You were there. You know what you asked for, what it got wrong on the first attempt, and which part you are least sure about.
Reviewing someone else’s is a different job, and it is the one that is becoming common. A pull request arrives, 900 lines across 14 files, the description reads like a well-organized summary of the diff, and the person who opened it was supervising three of these at once.
The usual code review advice assumes the author had intent you can ask about. Here the intent lives in a conversation you did not see, and the description may be a summary of the code rather than a statement of the goal. That changes the order of the review.
Reconstruct the goal before reading the diff
Start with the linked issue, the bug report, the failing test, the scan finding. Not the pull request description.
The description of an agent-authored change is frequently generated from the diff. That makes it an accurate account of what the code does and a useless check on whether the code does the right thing. If the patch solved the wrong problem, the description will confidently describe the wrong problem being solved.
Write one sentence in your own words: what should be true after this merges that is not true now. If you cannot write that sentence from the linked material, that is the first review comment, and it is more valuable than anything you would find by reading further.
Size the diff against the goal
Before line-by-line reading, look at the shape:
git diff --stat main...HEAD
Ask whether the file list matches the sentence you just wrote. A tenant-scoping fix that touches the router, two components, a migration, the lockfile, and a test helper might be correct. It needs an explanation that the description probably does not contain.
Specific things to notice in the file list:
- Lockfile changes on a patch that adds no feature. A dependency arrived. Find out which one and why.
- Config and CI changes bundled with application code. These are the changes that pass review by being boring, and they are the ones that alter what the merge gate checks.
- Generated files committed alongside handwritten ones. Regenerate them from the committed inputs and confirm you get the same bytes.
- Formatting-only churn across files unrelated to the change. It hides the real diff. Ask for it to be split, and do not accept “the formatter did it” without checking whether the formatter config also changed.
- Deleted files. Rare, and worth a direct question every time.
Read the tests first
This is the biggest departure from how I review human-authored code.
An agent that is optimizing for a green run has two ways to get there, and only one of them involves fixing the bug. So I read the test diff before the implementation diff, and I read it looking for dilution rather than coverage:
- An assertion that got narrower.
toBeGreaterThan(0)where the old test checked a value. - A negative case that disappeared. The test that proved the other tenant gets a 403 is the one that matters.
- A
skip, anonly, or an increased timeout. - A snapshot updated in the same commit as the behavior change, which accepts whatever the new code produces.
- A new mock that sits exactly on the boundary the fix was supposed to protect.
- A test whose name describes the fix rather than the behavior. “handles the tenant check correctly” tells you nothing; “returns 403 when the invoice belongs to another team” tells you what is guaranteed.
If a test was added, check that it fails without the fix. You can do this from the pull request branch:
git stash push -- src/ # keep the tests, revert the implementation
npm test -- path/to/new.test.ts # expect a failure, for the right reason
git stash pop
A test that passes on the old code is not a regression test. It is documentation with a green checkmark, and it will not catch the recurrence.
Follow the data, not the structure
Agent-written code is usually well-organized. It has helpful names, small functions, and reasonable file placement. That structure makes it read as reviewed, which is exactly why it is worth ignoring during the review.
Pick the user-controlled input and trace it to the side effect. For a request handler: where the input arrives, what validates it, where the authorization decision is made, what reaches the database or the network, what comes back, and what gets logged.
The recurring failures I see are not exotic:
- The authorization check exists but runs after the record is fetched, and the error message confirms whether the record exists.
- Error handling widened. A
catchthat used to handle one case now handles everything and returns an empty result, so the failure becomes a silent wrong answer. - A retry added around a call that is not idempotent.
- Validation on the shape of the input but not the ownership of the thing it names.
- A new query inside a loop that was previously a single call.
None of these are visible from the structure. All of them are visible from following one value through the change.
Ask the questions the diff cannot answer
Some things you can only get from the author, and the useful version of the question is specific.
Not “did you test this?” but “what happens when two of these arrive at the same time?” Not “is this safe?” but “which of these paths runs for a user who is not signed in?”
If the answer comes back as a restatement of what the code does, ask for the evidence instead: the failing test before the fix, the log line from a real run, the response from the deployed environment. Verifying an AI-generated patch is mostly the discipline of insisting on evidence that is independent of the explanation shipped with the change.
It is also fine to ask what the agent was told. The prompt is genuinely useful review context, and there is no reason to treat it as private. A prompt that says “fix the failing test” produces a very different patch from one that says “the tenant check is missing on this route.”
Know when to send it back
Stop the review and ask for a smaller change when:
- The diff is too large to hold in your head as one decision.
- The goal cannot be stated without reading the implementation.
- A test was weakened and the reason is not in the description.
- Dependencies, permissions, or configuration changed without a stated need.
- The patch touches a trust boundary and there is no negative test.
- Nobody on the team can explain why the fix works.
That last one is the real gate. A change nobody understands is a change nobody can debug at 2am, and the fact that it was cheap to produce does not make it cheap to own. Splitting it into three reviewable pieces costs an hour now and is the only version of this that scales.
What to automate
The mechanical parts of this list should not be a human’s job. Type checking, lint, the full test run from a clean checkout, a build, a dependency audit, and a check that generated files match their inputs are all things CI does better than a reviewer with 14 files open. The pre-commit hooks catch the fast ones before the pull request exists.
What is left for the reviewer is the part that requires knowing what the change was for: whether the goal is right, whether the tests would catch the recurrence, and whether the trust boundaries survived. SiteCMD’s deploy risk preview compares a branch against the last known-good scan so the review has an outside opinion about what the change did to the running site, which is a useful counterweight when the only other account of the change was written by the thing that made it.