AI-Built Software

What to Look For When Reviewing AI-Generated Code

AI-generated code fails in recognisable ways. These are the patterns worth checking deliberately, and why ordinary review habits miss most of them.

Reviewing AI-generated code is harder than reviewing code a colleague wrote, and the reason is that it reads better. The naming is consistent, the structure is tidy, the comments explain what the function does. Nothing about the surface invites the suspicion that usually makes a reviewer slow down.

The habits most teams use for review were built for human authorship. You skim the parts that look routine and concentrate on the parts that look difficult, because a person who found something difficult tends to leave traces of the struggle. Generated code leaves no such traces. It is uniformly confident, and it is confident about the parts it got wrong.

What follows are the patterns that recur often enough to be worth checking deliberately, whatever the code looks like.

Confident code for a situation that does not exist

The most expensive category is code that calls something plausible. A method name that fits the library's conventions perfectly and does not exist in that version. A configuration key that was real two major releases ago. A field on an API response that the vendor renamed.

Some of these fail loudly at the first run and cost nothing. The dangerous ones are the near-misses: an argument passed in the wrong position where both are strings, a date parsed with an assumed format that happens to work for the first eleven months of the year, a currency conversion applied in the correct direction for the only case anyone tested.

The check is to verify external calls against the documentation for the version you actually have installed, rather than against how reasonable they look.

The happy path is finished and nothing else is

Generated code implements what was described. If the description was a working import, the result is a working import, and everything outside that description is absent rather than wrong.

Read the code for what happens when the input is malformed, when the remote service is slow, when a job is interrupted halfway, and when the same operation runs twice. In our note on production readiness we treat this as the first thing to establish, because it is the behaviour least likely to have been specified by anyone.

The specific question worth asking of every write operation is what state the data is left in if the process stops at the worst possible moment. That has an answer in well-built software, and the answer is usually missing here.

Repetition where a decision should have been

Ask for the same kind of thing five times and you get five reasonable implementations of it. Each one works. Between them they encode five slightly different opinions about how errors are handled, how dates are stored, how a null is represented and what a failed lookup returns.

None of that is a defect on the day it is written. It becomes one the first time somebody needs to change the rule, because the rule now lives in five places and only three of them get updated.

This is the same architectural discipline question that arises in any system assembled quickly. The review job is to notice where a concept has been implemented repeatedly rather than decided once.

Security defaults nobody chose

Authorisation is the common gap. The interface hides the button, so the demonstration is convincing, and the endpoint behind it accepts any authenticated request. Anyone who can read the network tab can reach data belonging to another customer.

Alongside that: credentials committed into the repository because the example in the training data had them there, permissive CORS because it made local development work, database access wider than the application needs, and error responses that return a stack trace to the caller. Each is a default that a person would have had to actively choose, and nobody chose it.

Security is a governance responsibility before it is a technical one, and this is where that shows up most directly.

Dependencies that arrived without a decision

A generated solution reaches for packages freely. Some are the obvious industry choice. Some are abandoned, some are a single maintainer's side project, and occasionally the name is very close to a well-known package without being it.

Check the dependency list as a list. How many were added, when each was last released, what licence each carries, and whether any of them duplicate something the project already has. This takes ten minutes and it is the highest-value ten minutes in the review.

Making the review proportionate

None of this argues for reviewing every line by hand, which would spend the advantage that produced the code in the first place. It argues for putting the attention where the consequences are.

Anything that moves money, exposes customer data, writes to a system of record, or runs unattended deserves a line-by-line read by somebody experienced. Everything else can rely on tests, static analysis and the pipeline. Deciding which is which is the judgement that makes the review worth having, and it is the part that cannot be automated.

If nobody in the business is positioned to make that call, technical assurance exists for it. If you want the guardrails in place so this catches itself next time, see setting up a team to build with AI safely.

Next Step

Turn the issue into a structured decision.

If the article reflects something happening inside your platform, the useful next step is to understand where control is being lost and what should be governed first.