Rill
← All articles

Guides

AI code review without overengineering

The Rill Team

You ask an AI to review a small change. It flags a possible race and suggests retries, a queue, and a fallback. The next review finds a bug in the fallback.

Before accepting another fix, identify what can fail, label the evidence, and weigh the fix's maintenance cost. Test the uncertain behavior before adding another mechanism.

Ask for a concrete failure

Have the reviewer finish this sentence:

If these conditions occur, this code does this, and the user experiences this consequence.

“There could be a race” leaves the finding unfinished. Which operations overlap, and can they overlap in today's code? Does the user get stale results, lose an edit, or gain access to someone else's account?

Then label the evidence:

  • Demonstrated: a test shows the failure under stated conditions.
  • Reachable code path: you can trace the preconditions and failure in the source.
  • Hypothesis: plausible, but not yet established.

One clean browser run can't rule out a source-level flaw. A confident explanation still needs evidence that the path is reachable.

Count the cost of the fix

Read the patch as carefully as the finding. Does it add a second source of truth, a dependency, or a queue to run? Could retries repeat a side effect? How many new states must the next developer understand?

Martin Fowler's explanation of YAGNI covers the ongoing cost of speculative code. AI can write it quickly. You still carry the maintenance cost.

Ask for the smallest change that fully fixes the failure. An existing safeguard may already cover it. A bigger patch can still earn its place if it removes duplication or fixes a repeated failure.

Test the search race before adding a queue

Fieldlist is a fictional customer-search app. The code illustrates response ordering; it is not a report of a real app test.

The agreed change is an empty state for searches that find nothing. The AI reviewer notices that two searches could run at once and recommends a request queue, retries, and a synchronization manager.

Check whether an older response can arrive last and overwrite current results. A simplified implementation might look like this:

// Whichever response arrives last updates the screen.
async function search(query: string) {
  setResults(await fetchResults(query));
}

For a single search controller, a request counter can express the rule that only the latest request may update results:

let latest = 0;

async function search(query: string) {
  const id = ++latest;
  const results = await fetchResults(query);
  if (id === latest) setResults(results);
}

Each search instance needs its own counter. This sketch covers successful responses; fit it to the screen's existing error and lifecycle handling. Check whether the implementation already rejects stale results before adding another guard.

If you're still unsure, write a focused test that controls which response returns first. Typing fast won't establish the order. If the test reproduces a visible failure, record that behavior with Rill so reviewers can see it.

A screen recording can include optional narration. Supported browser-agent recording with the Rill CLI can also capture console messages, network activity, and a timeline. Capture details depend on the setup and settings.

Let the evidence guide the decision:

  • Stale results overwrite current results: evaluate a local guard like the one above against your implementation.
  • Existing code rejects stale responses and the test confirms it: close the concern with that evidence.
  • Inconclusive: note what you checked and decide whether the potential harm justifies another bounded investigation.

Keep the empty-state change scoped. A confirmed race may need a separate fix or may block release, depending on its impact. Make that decision explicit.

Take rare high impact failures seriously

A display glitch and a duplicated payment need different responses. Investigate credible paths to data loss, unauthorized access, or irreversible actions even when they're hard to reproduce.

Skip “one in a million” guesses. Can a supported workflow reach that state? Can someone trigger it deliberately? What protections apply? Code analysis or a controlled test may establish a defect without a browser reproduction.

Give the AI reviewer a narrower brief

Set the intended behavior, files in scope, and non-goals. If you only want a review, say so.

Review this change against its intended behavior. Return findings
and proposed fixes. Do not modify code.

For each finding, give the failure, its preconditions, and the user
impact. Cite code or test evidence. Mark hypotheses, optional
hardening, and anything unverified.

Weigh the maintenance cost of each proposed fix. Prefer the smallest
fix that fully solves the problem. Check existing safeguards first.
Take credible security and data-integrity risks seriously even when
hard to reproduce. Flag fixes that materially expand scope.

Trace every decision to evidence

Keep the relevant recording beside the review comment so another reviewer can watch what happened. It documents one run; tests and code analysis establish broader behavior.

End each finding with a decision: fix an established defect, investigate a specific unknown, defer an optional improvement with a reason, or close a concern the evidence contradicts. Note what would reopen it.

If you change the app, verify the fix with the same steps before and after.

Use test data and review recordings before sharing. Anyone with an active share link can view the recording and included comments. Rill doesn't mask private information in the video.

Try it on your next review

Try Rill free with one browser interaction worth reviewing. Record your screen, capture your coding agent's browser work, or give Rill a public URL and a task to run in its cloud browser. Use the recording setup guide to choose a route.

To review an existing recording with ChatGPT, connect the Rill plugin. Bring the replay and the finding together before deciding what to change.