Skip to main content
Back to Blog
Article

How to Review AI-Generated Code Before It Hits Production

AI generates code faster than I can type it. That’s not the problem. The problem is that it generates code that looks correct, sounds confident, and is sometimes subtly wrong. After months of daily use, I’ve built a review process that catches the things AI consistently misses.

Why AI Code Needs More Review, Not Less

The instinct when AI writes your code is to review less of it. It was generated by a model trained on millions of repositories. It must know the patterns better than you do.

It doesn’t. AI knows the average of what it’s seen. It doesn’t know your business logic, your security requirements, or the edge case that only shows up when a specific user does something unexpected on a specific day.

AI-generated code is syntactically correct, follows common patterns, and compiles without errors. It also:

  • Introduces security vulnerabilities wrapped in clean, readable code
  • Misses edge cases that a developer with domain knowledge would catch
  • Uses patterns that are correct in isolation but wrong in your specific context
  • Writes comments that describe what the code should do, not what it does

More code, faster, with higher confidence. That means you need a sharper review process, not a quicker one.

The Three-Layer Review

I review AI-generated code in three passes. Each catches a different category of problem.

Layer 1: Does it actually solve the problem?

AI is excellent at generating code that solves the problem you described. It is less excellent at solving the problem you actually have.

Before I look at the implementation, I ask myself:

  • Did it solve what I asked, or did it solve a simpler version of what I asked?
  • What assumptions did it make about my data, my environment, my users?
  • If I gave it this prompt again tomorrow, would I describe the problem differently?

I’ve had AI generate a validation function that checked the wrong field. An API endpoint that returned data in a format nothing in my app expected. A database query that worked for the happy path but fell over on NULL values. All syntactically correct. All useless.

This layer isn’t a line-by-line inspection. It’s a conversation with the code. Does this do what I actually need?

Layer 2: The security and edge case pass

This is where I slow down. I look at the code specifically for:

Input validation. AI tends to trust its own output. If it generates a form handler, does it validate on the server side? If it generates an API endpoint, does it handle malformed input gracefully, or does it assume the client will always send valid data?

Authentication and authorisation. AI will happily generate an endpoint that fetches user data without checking who’s asking. It knows how to write auth()->user() but it doesn’t know that your app requires role-based access control on that specific route.

Error handling. AI generates try/catch blocks that log errors and return generic messages. Fine for boilerplate. But does it handle the specific errors that matter in your context? What happens when the database connection drops mid-transaction? When a third-party API times out?

Data exposure. AI will serialise entire models into API responses, including fields that should never leave the server. Password hashes, internal IDs, tokens, all of it. You have to check what’s actually in the response, not just that the response exists.

I have a mental checklist now. Not exhaustive, but it catches the common patterns:

  1. Where does data come from and where does it go?
  2. Who can trigger this code path?
  3. What happens if any of the inputs are wrong, missing, or malicious?
  4. What happens if an external dependency fails?

Layer 3: Does it match my codebase?

AI generates code in a vacuum. It doesn’t know your naming conventions, your existing helpers, or the architectural decisions you’ve already made.

I check for:

  • Naming conventions. Did it pick up your conventions, or did it import a different style from its training data?
  • Patterns. Does it use your existing utilities, or did it reinvent something that already exists? AI is particularly bad at this. It will generate a date formatting function when you already have three in your helpers.
  • Dependencies. Did it import a library you don’t use? Did it suggest an approach that contradicts an architectural decision you’ve already made? Did it pull in an older, unsupported version of a package? AI’s training data has a cutoff, so it happily suggests versions that have been deprecated for months.
  • Test coverage. If it generated tests, do the tests actually test the right things? I’ve seen AI generate tests that pass because they test nothing meaningful.

This is also where I check if the code does less than it could. AI tends to overbuild. If I asked for a simple function, did it generate a simple function, or did it add factories, decorators, and abstractions I didn’t ask for?

What I Actually Do in a PR

When I review AI-generated code in a pull request, the process looks like this:

  1. Read the prompt I gave. Is the PR solving what I asked for?
  2. Read the diff, not the full file. What actually changed?
  3. Check every external input. Where does data enter the system?
  4. Check every external output. Where does data leave the system?
  5. Check error paths. What happens when things go wrong?
  6. Check for things that aren’t there. Missing validation, missing auth checks, missing tests.
  7. Run it. Does it work the way I expected, or the way AI expected?

The last one matters more than people think. AI-generated code that compiles and passes tests can still be wrong in ways that only running it reveals. The UI looks right but the button doesn’t respond. The API returns 200 but the data is empty. The migration runs but deletes data it shouldn’t have.

What I’ve Learned the Hard Way

AI is most dangerous when it’s 90% right. The 10% that’s wrong is the bit you’d have spotted straight away if the whole thing was rubbish. But because the 90% looks perfect, your brain fills in the gaps.

Comments are unreliable. I’ve found functions where the comment says “validates user input” and the function body does no validation at all. The comment was generated first, and the code was written to match the comment’s intent, not its literal meaning.

AI has favourite patterns and applies them everywhere. Once you notice that every service class gets a static make() method, or every controller returns the same response format regardless of context, you start seeing it everywhere. It’s not always wrong, but it’s not always right either.

Tools help, but they have limits. Rector, PHPStan, static analysis tools, they catch the mechanical stuff. The judgement calls, the “this works but it’s not how we do things here” calls, those still need a human looking at the code.

The most useful review skill is knowing what’s missing. AI-generated code is good at being present. It’s bad at remembering that the auth middleware needs to be applied to this route, or that the rate limiter should be in place before this endpoint goes live, or that this function needs a mutex because two workers can hit it at the same time. The code that isn’t there is harder to spot than the code that is.

Tools That Help

Reviewing AI code is one thing. Reducing how much you need to review in the first place is better. Two tools I use daily that tackle the overbuilding and convention-matching problems directly.

Code editor showing AI-generated code with a context menu open
Photo by Daniil Komov on Unsplash

Laravel Boost

If you work in Laravel, Laravel Boost is an MCP server that gives AI agents real context about your specific application. Without it, AI generates generic Laravel code. With it, AI knows your routes, your database schema, your package versions, and the conventions your codebase already follows.

The practical effect is that the code AI generates actually looks like it belongs in your project. It uses your existing patterns. It imports your existing helpers. It doesn’t suggest a package you already have a replacement for. The Layer 3 review gets significantly shorter because the code arrives closer to correct.

Boost also ships version-aware guidelines for over 16 Laravel ecosystem packages. If you’re on Livewire 3 and Tailwind 4, the AI gets instructions specific to those versions, not generic advice that might reference deprecated methods.

Ponytail

Ponytail is an agent skill that attacks the overbuilding problem head-on. Before AI writes any code, Ponytail forces it through a decision ladder: does this need to exist? Does the standard library already do it? Can it be one line? Only if every rung fails does it write the minimum custom code required.

The numbers are solid. Benchmarked against a real codebase (FastAPI and React), Ponytail cut lines of code by 54% on average, with no reduction in safety or correctness. The code that gets written is the code that’s actually needed.

I run Ponytail in full mode for day-to-day work. It catches the pattern I described earlier where AI adds factories and decorators to a simple function. It forces the question “can this just be a function?” before the agent reaches for a class hierarchy.

Between the two, AI-generated code arrives more idiomatic and more concise. The review shifts from “is this even the right approach?” to “does this solve my specific problem?” which is a much better use of the review time.

The Governance Question

If you’re working in a team, this gets harder. Who reviews AI-generated code? The same people who review human-written code? Do you need different standards? Should you know that a piece of code was AI-generated before you review it?

I don’t have clean answers yet. The code doesn’t care who wrote it. It either works, is secure, and is maintainable, or it isn’t. The review process should enforce those standards regardless of the source.

Transparency still matters though. If a PR is mostly AI-generated, say so. Not because it needs different review, but because it gives the reviewer context about where to focus their attention.

The Short Version

Review AI code the same way you’d review any code, just with extra attention to the specific ways AI tends to fail. Security, edge cases, missing pieces, and whether the thing actually solves the problem you asked it to solve.

Give your AI agents proper context with tools like Laravel Boost and enforce minimal solutions with something like Ponytail. You’ll spend less time reviewing bad code and more time on the judgement calls that actually matter.

AI writes the code. You’re still responsible for it.

Comments