Reviewing Your Own Diff With AI: The Prompt That Actually Finds Bugs
Asking an AI to 'review this code' returns style opinions, not bugs. A constrained prompt — diff only, named failure axes, concrete failure scenarios, ranked by severity — turns it into a checklist that reads your code.
Hessam Rastegari
Senior Android Developer · 12 years shipping Android
TL;DR — Generic review prompts produce generic output: renames, extractions, comment suggestions. To find real defects, constrain the question. Give the model the diff rather than the file, name the failure axes you care about (concurrency, lifecycle, boundaries, error paths, compatibility), demand a concrete failure scenario for every claim, state what's already handled, and make it rank by severity and flag its own uncertainty. This does not replace human review — it's a checklist that can read code.
Why "review this code" fails
A model asked an open question answers with whatever is easiest to justify. For code, the cheap answers are stylistic: naming, function length, missing comments. They're safe, always technically arguable, and almost never the reason production breaks.
Worse, that output is expensive to read. Twenty low-value suggestions train you to skim, and skimming is how you miss the one that mattered. The goal isn't more feedback. It's a higher ratio of findings to words.
Give it the diff, not the file
Review is a question about change. When you paste an entire file, you invite commentary on code that has been in production for two years and isn't under discussion.
git diff main...HEAD -- app/src/main/java/com/example/player/
Paste that. If a hunk needs surrounding context to be judged, add the specific function it calls — not the module. The narrower the surface, the more the model's attention lands on what you changed.
Name the failure axes
Unprompted, a model won't know which risks matter in your codebase. Enumerate them:
Check specifically for: (1) coroutine scope and cancellation correctness, (2) Android lifecycle — work that outlives the screen, (3) null/empty/boundary inputs, (4) error paths and what the user sees when they fail, (5) behaviour changes for existing callers.
This is the single highest-leverage line in the prompt. It converts an open-ended essay prompt into a structured pass. On Android I keep lifecycle and cancellation permanently in that list, because they're the categories where a bug survives code review, survives QA, and shows up as a leak or a crash three releases later.
Demand a failure scenario for every claim
The rule that separates signal from noise:
For each issue, give the concrete input or state that produces the wrong result, and what the wrong result is. If you cannot name one, say so and mark it as speculative.
A model that can invent a trigger has probably traced the logic. A model that can't is pattern-matching on something that merely looks dangerous. Making it write the scenario forces the distinction into the open, where you can see it.
// The kind of finding this surfaces:
// "If refresh() is called while a previous collect is active, `job` is
// overwritten without cancelling the old one — two collectors write to
// _state and the older one wins on slow networks."
private fun refresh() {
job = viewModelScope.launch { repo.stream().collect { _state.value = it } }
}
That's a real bug with a stated trigger. "Consider using a more descriptive name than job" is not.
State what's already handled
Half of a model's default output is about concerns your architecture has already solved. Pre-empt it:
Assumptions: this runs on the main thread; the repository is already thread-safe; DI guarantees a single instance per screen; we intentionally swallow errors here because the caller retries.
Every assumption you state is a paragraph you don't have to read and dismiss.
Make it rank — and admit doubt
Finish with:
Order findings by severity. Mark each CONFIRMED (you traced the code path) or PLAUSIBLE (it looks wrong but you'd need to see other files).
The PLAUSIBLE bucket is where I spend my time. Those are the spots where the model noticed
something it couldn't fully verify — which is frequently exactly where a subtle bug lives, because
it's also where a human reviewer would need context they don't have.
What this is, and what it isn't
Used this way, AI is not a reviewer. It doesn't understand your product, your users, or why that weird branch exists. It's a tireless checklist that can read code — and its value is that it never gets bored on hour three of a large diff, which is precisely when I do.
The discipline that makes it work is the same discipline that makes human review work: ask a specific question, insist on evidence, and rank by consequence. If you wouldn't accept "this feels wrong" from a colleague, don't accept it from a model.