7 Checks to Run Before Trusting AI-Generated Code
Traditional bugs announce themselves. A typo breaks the build, a missing import throws at startup, a type mismatch fails compilation. AI-generated code tends to fail in a different register: it is plausible. Models are trained on enormous volumes of real code, so they reproduce the shape of correct solutions, including the confidence. A function that declares an integer return type while its dictionary holds strings reads perfectly well to a human skimming a diff. A method that resembles the rest of an SDK looks like it belongs there, whether or not it exists.
That is the verification gap. Syntax is cheap to check and largely automated. Semantics, meaning whether the code does the right thing under real conditions, is expensive and stubbornly human. GitHub’s guidance on AI-generated code makes the same point: output from these systems can be semantically incorrect even when it is syntactically valid. The code compiles, it runs, and it may even pass the tests that shipped with it, while embedding wrong assumptions about how an API behaves, who is allowed to call a function, which package actually exists, and what happens when the network drops halfway through a transaction.
The fix is not to distrust AI code on principle. It is to replace the vague question “does this look good?” with a structured sequence of checks, ordered so that the cheapest and most human verification comes first and the specialized tooling comes later. Here are seven checks worth running before anything generated by a model reaches production.
Check 1: Read the diff before you open a chatbot
The instinct when reviewing AI output is to paste it into another model and ask whether it looks correct. Resist that. The first verification step is a human running git diff --stat.
Coding agents with repository-wide access routinely do more than they were asked. A two-file bug fix becomes a refactor, a dependency bump, a configuration change, and a formatting sweep across the codebase. The stat output makes scope creep visible in seconds. If you asked for a small fix and the diff reports hundreds of changed lines, stop and ask why the task required them. Before evaluating whether the code works, confirm the agent solved the task you gave it rather than a larger one it invented.
Understanding how each assistant behaves under a real workload helps here. Our AI Coding Assistants Compared: Cursor, GitHub Copilot, Claude Code, Windsurf and Replit Agent After a Month of Real Work shows how much the tools differ in how far they stray from the prompt, which is exactly the behavior the diff check is designed to catch.
Check 2: Let deterministic tooling do the syntax work
No human should spend attention on problems a linter catches in milliseconds. Run the formatter, the linter, and above all the static type checker before reading a single line.
Type checking earns its place at the top of the list because it targets an AI-specific weakness. Models connect values that look compatible. They will happily return a dictionary of strings from a function annotated to return an integer, because in the local context the two feel adjacent. A type checker does not care about feelings. It resolves the mismatch and hands you a precise location. Treat a clean type check as the entry ticket to review, not as evidence of correctness.
Check 3: Verify every dependency against a registry
This is the check with no real precedent in human-written code, and it deserves its own category. Models invent package names. The names sound reasonable, follow the conventions of the ecosystem, and sometimes do not exist at all.
The scale is documented. One 2025 analysis of LLM-generated code across Python and JavaScript measured package hallucination rates of 5.2% for commercial models and 21.7% for open-source models, meaning the share of suggested packages that did not exist in the registry. Roughly one in five dependencies suggested by an open-source model was fabricated.
That number becomes an attack surface through slopsquatting: registering a commonly hallucinated package name so that developers who install it on faith pull in someone else’s code. Every dependency an AI adds should be checked against an authoritative registry (PyPI, npm, crates.io, Maven Central) before it enters a lockfile. Confirm the package exists, that its maintainer history is plausible, and that it is the library you actually meant.
Check 4: Check APIs against current official documentation
Package hallucination has a sibling: method hallucination. A generated call can resemble the rest of an SDK closely enough that reviewers assume it exists. Resemblance is not evidence.
Confirm every unfamiliar external API against current official documentation. The risk concentrates in fast-moving territory: AI SDKs that ship breaking changes monthly, cloud APIs with deep service surfaces, and JavaScript frameworks where the ecosystem’s churn outpaces most training cutoffs. A model trained months ago will confidently call a method that was renamed, deprecated, or never existed. Every new package and every unfamiliar call should be traceable to a registry entry or an official doc page, not to the model’s assurance.
Check 5: Test beyond the happy path
Models generate the happy path because that is what dominates examples and documentation. The success case is well represented in training data; the malformed input, the empty result set, and the timeout are not.
For each generated feature, exercise at least four categories of behavior: valid input, invalid or malformed input, boundary and empty cases, and failure of external dependencies such as the network, the database, or the file system. Then audit the tests themselves. A model asked to make a failing suite pass may “fix” the problem by deleting the failing test or weakening its assertion. Diff the test files as carefully as the source.
For critical functions, consider writing the test before showing the implementation to another model. A test authored independently encodes your understanding of correct behavior rather than a model’s.
Check 6: Treat security as a separate test
Code that works and code that is safe are different claims. Functional tests will not tell you whether an authorization check can be bypassed.
When generated code touches user-controlled input, authentication, databases, files, shell commands, or external URLs, switch to an adversarial frame of mind. Run static security tooling as a matter of course: Semgrep, which can automatically select relevant scanning rules for a codebase; Bandit, which walks Python’s abstract syntax tree looking for common security problems; and CodeQL, GitHub’s code-scanning engine for supported languages. Wire these into your CI pipeline so they run on every change rather than relying on a manual pass. Add secret scanning on top, since an assistant may hardcode a credential it saw in its environment or context. No untrusted input should cross a security boundary without a clear account of how it is validated, escaped, authorized, or isolated.
Check 7: Examine failure behavior deliberately
AI-generated code is at its most convincing when everything works. The interesting question is what happens when it does not.
Error handling is where appearances mislead most. Code can look robust because it contains try/except blocks, while the handling actually suppresses failures. A single catch-all wrapped around several unrelated operations makes debugging far harder than an honest crash. Ask specific questions. If step three of a sequence fails, are steps one and two rolled back? Can two concurrent requests modify the same record without coordination? Does a retry duplicate a side effect? These are the failure modes that reach production, and they are the ones a passing test suite is least likely to surface.
Turning the seven checks into a pre-merge checklist
The checks only pay off if they become routine. Written as a pre-merge checklist for AI-assisted work, they look like this:
- Diff scope reviewed; change size matches the task.
- Formatter, linter, and type checker pass.
- Every new dependency verified in its registry.
- Every unfamiliar API confirmed against current docs.
- Tests cover invalid input, boundaries, and dependency failures; nothing deleted or weakened.
- Security review done for input, auth, data, file, shell, and URL handling; static analysis and secret scanning run.
- Failure paths, rollback, and concurrency examined explicitly.
None of this requires abandoning AI assistance. It requires treating generated code as a draft from a fast, confident, occasionally inventive collaborator, and applying the same skepticism a careful engineer would apply to a patch from a stranger. As agents take on larger tasks, the volume of generated code entering repositories will only grow. The seven checks scale because they are ordered by cost: a human glance at a diff first, deterministic tools second, registries and documentation third, and deliberate adversarial thinking where the risk is highest.