Articles › AI

Reviewing AI-written code: what AI gets wrong and a checklist before merge

AI writes code fast, but it invents packages, swallows errors and writes tests that test nothing. A review checklist, small diffs, run before you read, a human signs off.

Beginner 7 min read October 22, 2026

What you will learn
  • AI invents package names that do not exist, and it repeats the same fake names, so attackers can register them first (slopsquatting)
  • Check every new dependency against the registry before installing; it is one command
  • Have the AI deliver small diffs, run tests and linters before reading, then read the risky parts closely
  • The person who merges owns the code, not the AI

AI writes code many times faster than a person, but that speed moves the work onto the reviewer. Code that looks tidy and runs can call a package that does not exist, swallow an error, or open a hole through an unsafe default. This article lists what AI gets wrong most often and gives a checklist you can use before you press merge.

Facts checked against the USENIX Security 2025 and ACM CCS 2023 papers, Veracode’s 2025 report and Google’s code review guide. Commands tested with npm 10.9, pip 25.0 and Python 3.12.

Why AI code needs a different review

When people write bad code it usually shows: odd names, half-finished logic. AI code is wrong while looking right: clean formatting, comments everywhere, sensible function names. That is what lulls a reviewer.

Numbers you can check:

  • Veracode 2025 tested more than 100 models on 80 tasks and found vulnerabilities in 45% of test cases. Java was worst at over 70%; Python, C# and JavaScript sat between 38% and 45%. Larger models did not do significantly better than smaller ones.
  • Perry et al. (CCS 2023): participants with an AI assistant wrote less secure code than those without, and were more likely to believe their code was secure. Those who trusted the AI less and reworked their prompts produced fewer vulnerabilities.

So the problem is not which model you pick. It is what happens after the AI hands the work over.

What AI gets wrong most often

1. Invented APIs and packages

The paper “We Have a Package for You!” (USENIX Security 2025) had 16 models write 576,000 Python and JavaScript samples. Of 2.23 million package references, 440,445 (19.7%) named packages that do not exist, 205,474 unique names. Commercial models averaged at least 5.2%, open-source models 21.7%.

The dangerous part: the fake names repeat. Asked the same thing 10 times, 43% of hallucinated names came back every time, and 58% came back more than once. So an attacker can register that name on npm or PyPI, put malware in it, and wait for someone to npm install what the AI suggested. This is slopsquatting, a term coined by Seth Larson of the Python Software Foundation as a play on typosquatting.

Invented APIs work the same way: a method that does not exist in the version you use, an option the library never had. It fails at runtime, not when you read it.

2. Missing edge cases

AI is good at the happy path. It forgets the empty list, zero, negatives, time zones, non-ASCII input, the request that arrives twice. Ask every time: what happens if the input is empty, huge, or malformed?

3. Silently swallowed errors

try:
    save_order(order)
except Exception:
    pass  # the save failed and nobody will ever know

Tests pass, the screen shows no error, and data is gone. Every except should do one of three things: actually handle it, log it and re-raise, or return something the caller knows means failure.

4. Tests that test nothing

def apply_discount(price, pct):
    return price * (1 - pct / 100)

def test_apply_discount():
    result = apply_discount(100, 10)
    assert result is not None   # passes even if the formula is wrong

You want assertions on the correct values, plus the edges:

import pytest

def test_apply_discount():
    assert apply_discount(100, 10) == 90
    assert apply_discount(100, 0) == 100
    assert apply_discount(100, 100) == 0

def test_discount_over_100_is_rejected():
    with pytest.raises(ValueError):
        apply_discount(100, 150)   # fails until the code validates pct

Other common forms: mocking the very function under test, or changing the expected value to match what the broken code returns. When an AI “fixes” a failing test, check whether it changed the code or the test.

5. Unsafe defaults

Veracode found models failed to defend against cross-site scripting (CWE-80) in 86% of cases and log injection (CWE-117) in 88%. What we see in real work:

  • verify=False on HTTPS calls to “make it work”
  • CORS * or debug=True that ships to production
  • SQL built by string concatenation instead of parameterized queries
  • user input dropped straight into HTML or logs
  • a key or password hard-coded “as an example” and never removed

Worked example: the odds of at least one fake package

Say the AI suggests 10 dependencies for a project, and each has a 5.2% chance of being fake (the commercial-model floor from the paper). Assume each is independent, which is a simplification to make the point.

  1. Chance one package is real = 1 − 0.052 = 0.948
  2. Chance all 10 are real = 0.948¹⁰ ≈ 0.586
  3. Chance at least one is fake = 1 − 0.586 ≈ 41%
Dependencies suggestedat 5.2%at 21.7%
10≈ 41%≈ 91%
20≈ 66%≈ 99%

Real rates depend on the model and the task, but the lesson holds: check every one, not a sample.

Check a package before installing

A name that does not exist returns 404 straight away. For one that exists, look at when it was created; a package a few days old with no users deserves suspicion.

# npm: does it exist, when was it created
npm view <package-name> time.created

# PyPI: 404 means no such package
curl -s -o /dev/null -w "%{http_code}\n" https://pypi.org/pypi/<package-name>/json

Then open the package page and look at the source repo, the maintainers and the download count. Version pinning and lockfiles are covered in supply-chain safety for npm and pip.

The checklist before merge

#CheckFastest way
1The diff is small enough to readgit diff --stat; if it is several hundred lines, have the AI split it
2New dependencies exist and are trustworthynpm view / PyPI JSON for each one
3It actually runstests + linter + type check, before reading
4Tests assert correct valuesread tests before code; look for is not None, self-mocks
5Errors are not swallowedsearch for except: pass, empty catch {}
6Edge casesempty / zero / huge / duplicate input
7Security settingssearch for verify=False, CORS *, debug, string-built SQL, secrets in code
8Called APIs exist in your versionopen the docs for the version you pinned
9You understand every line you mergeexplain it to yourself; if you cannot, ask or rewrite
Small diffabout 100 lines a roundRun tests firstfails = send back unreadRead the testsdo they test anything?Read risky partsnew packages, errors, authHuman signs offa person owns the code
Review order for AI code: if it fails to run, do not spend time reading it

Small diffs, and run before you read

Google’s code review guide says about 100 lines is usually reasonable and 1,000 lines is usually too large. Small changes get a more thorough review, make bugs easier to spot and are easier to roll back. With AI this matters more, because it will happily hand you 1,000 lines in a minute. Ask for one step at a time, one step per commit.

The order we use:

  1. Run it first. Tests, linter, type check, build. If it fails, do not read it; send it back.
  2. Read the tests. Do they prove what you need?
  3. Read the risky code closely. New dependencies, input handling, auth, money, anything that deletes data.
  4. Try it for real. Open the screen, try the wrong inputs, not only the right ones.

If AI code runs on your laptop but breaks on the server, works on my machine covers how to prevent that.

AI can write the code, but it cannot sign for it. Whoever presses merge owns it.

Common mistakes

  • Judging by how it looks. AI code always looks good. Run it and check the assertions.
  • Installing whatever the AI names. Check the registry first, every time.
  • Letting the AI fix tests until they pass without checking whether it changed the code or the expected value.
  • Accepting a huge diff because you are in a hurry. Huge diff = skim = bugs slip through.
  • AI reviewing AI as the final gate. Fine as an extra pass; a person is the last one.
  • Forgetting the AI reads outside content. If an agent reads a web page or an issue and then writes code, that content can steer it. See defending against prompt injection.

Sources

FAQ

Can another AI do the review instead of a person?

As a first pass, yes; it catches things that are easy to overlook. Not as the last pass. A CCS 2023 study found people using an AI assistant wrote less secure code and were more confident it was secure. That overconfidence is exactly what a human reviewer is there to stop.

How small is a small diff?

Google's code review guide says about 100 lines is usually reasonable and 1,000 lines is usually too large. File count matters too: 200 lines in one file may be fine, spread across 50 files it usually is not.

All tests pass. Do I still need to read the code?

Yes. The AI wrote the code and the tests. If a test only checks that the result is not None, it passes even when the number is wrong. Read the tests first and check that they assert the right values.

Commands and settings were tested on sample systems. Try them on a test machine before production.