How to review AI-generated code before you push: a checklist
A practical checklist for reviewing what Claude Code, Codex or Cursor changed before you push: tests, migrations, auth, secrets, dependencies, errors and scope.
To review AI-generated code, read the diff rather than the agent's summary of it, and check first for the mistakes a passing test run hides: tests that were weakened, destructive or locking database changes, removed auth or validation, committed secrets, dependencies that do not exist, and errors that are silently swallowed. Then check that the change does what you asked and nothing more. The order matters: start with the changes that are expensive to undo once they ship.
The checklist below works for Claude Code, Codex, Cursor, Copilot or any other agent. It assumes the agent has finished and says the task is done, and that the tests are green.
0. Get the whole diff in front of you
Agents often commit as they go. Review everything since the branch point, not just the working tree.
git fetch origin
git log --oneline origin/main..HEAD # commits the agent made
git diff --stat origin/main...HEAD # files touched, lines changed
git diff origin/main...HEAD -- . ':(exclude)*.lock' # the change itself
git status --short # anything not yet committedLook at --stat before reading any code. A request to fix one bug that touched 40 files, a CI workflow and a lockfile is a finding on its own.
1. Tests: were they changed to pass?
GitHub's guidance on reviewing agent pull requests puts deleted tests first, and for good reason: agents are optimising for a green run, and editing the test is often the shortest path. Look for deleted test files, new skip markers, .only, removed or loosened assertions, expected values changed to match new output, and lowered coverage thresholds. This deserves its own guide: AI coding agents deleting tests. Check: test-integrity.
2. Anything destructive or irreversible
- Migrations that add a column with a default on a large table, add an index without a concurrent option, change a column type, or drop a column the running version of the app still reads. See migration-safety.
- Recursive deletes,
DROPandTRUNCATE,DELETEwithout aWHERE,git push --forcein scripts. See destructive-ops. - Changes to cron schedules, retries, timeouts and CI concurrency, which change production behaviour without changing features. See ops-deltas.
3. Auth, guards and validation
A new route with no auth check, or a webhook handler that does not verify the sender's signature, is easy to miss because the feature works in testing. So is a removed permission check, rate limit or validation call: an agent that hit a 403 in a test may have solved it by deleting the check. Search the removed lines, not just the added ones. See auth-surface and removed-guard.
4. Secrets and injection
- Credentials pasted into code, config or fixtures to get something working. If one is committed, rotate it. See secrets.
- Untrusted input concatenated into SQL, HTML, a shell command,
eval, or an outbound URL. See injection-sink. - GitHub Actions that interpolate issue or PR text into a script or an AI step, which lets anyone who can open an issue run commands. See workflow-prompt-injection.
5. Dependencies
Models sometimes import packages that do not exist. Spracklen et al. (USENIX Security 2025) measured package hallucination rates of about 5.2% for commercial models and 21.7% for open-source models in their study, and found many hallucinated names recur across runs, which makes them worth registering for an attacker. For every new import, confirm the package is in the manifest and lockfile and is the one you expect on the registry. Also check version bumps that cross a major version. See dependency-risk and dependency-bump.
6. Contracts and configuration
- Exported functions renamed, removed or re-signed while other code, or other services, still call the old version. See breaking-export.
- New environment variables read in code but not declared in
.env.example, deployment config or the settings schema. These work on the agent's machine and fail on deploy. See env-drift.
7. Error handling and runtime behaviour
- Empty
catch,except: pass,rescue nil, or_ = errin Go. These make tests pass by hiding the failure. See swallowed-errors. - Async calls that are not awaited, so their failures vanish. See unawaited-async.
- A database query, HTTP request or model call inside a loop, fine with three test rows and slow with thirty thousand. See io-in-loop.
- Files, sockets, cursors or intervals opened and never closed. See resource-leak.
8. Unfinished work and leftovers
TODO, stub functions that return a constant, placeholder URLs or IDs. See unfinished-work.@ts-ignore,# type: ignore,eslint-disableornoqaadded without a reason. See suppressed-diagnostics.debuggerstatements and strayconsole.logorprintcalls. See debug-leftovers.
9. Scope: does it do what you asked, and only that?
Re-read your original request, then read the diff as if a colleague sent it. Did it implement what you asked, or something adjacent? Did it touch CI configuration, infrastructure, generated files or unrelated modules? Is the change small enough to review properly? If not, ask the agent to split it. See scope-creep.
What no checklist catches
Many agent mistakes are about meaning, not form: a rate limit applied per request instead of per user, a filter on the wrong field, a header replaced instead of merged, a correct function called with arguments in the wrong order. Pattern checks cannot see those. They need a reviewer who knows what the code is supposed to do, human or model. The checklist exists to clear the mechanical problems quickly so that attention goes to the semantic ones.
How IntentiAI helps
Ship Check automates the mechanical part of this checklist. Run npx intentiai check after the agent finishes (or npx intentiai check --base main to cover everything since the branch point). It runs 21 deterministic checks over the diff with no model and no network, prints the most serious findings ordered by risk, and writes a full report to .intentiai/ship-check.html. Nothing leaves your machine unless you add --share. It exits 1 on a critical finding, so it can gate a pre-push hook (npx intentiai install-hook) or CI. npx intentiai init claude runs it automatically when Claude Code finishes a task.
For the semantic part, --agent claude (or codex, gemini) hands the change and findings to your own installed agent in read-only mode, and --task lets it compare what you asked for against what the code does. On the project's measurements, 13 of 14 Ship Check findings were correct on 30 recent commits from six projects in Go, Rust, Java, Python and TypeScript. Being deterministic, it misses mistakes about meaning. See Ship Check and the docs.