How to Inspect AI-Generated Diffs Before Accepting

A 200-line AI diff isn't safe just because it compiles. Read it in the right order.

Claude Code or Cursor hands you a 200-line diff, it compiles, tests are green, and you hit Cmd+Enter “Accept All.” Two hours later production has a weird bug — the AI deleted a “seemingly unused” boundary check; or replaced if (!user) with if (user === null) while callers pass undefined; or renamed an identifier and silently touched an unrelated context with the same name. These are all “diff compiles ≠ behavior unchanged” failures. This article gives a fixed reading order for AI diffs — deletions first, then renames, then additions — and how to quickly judge risk in each.

Common causes

Ordered by risk, highest first.

1. Deleted “seemingly dead” code that wasn’t dead

AI confidently deletes // legacy, // TODO: remove, and functions with no direct callers. But dynamic dispatch, CLI entry points, dynamic require, reflection, and exports referenced only in comments are invisible to static analysis.

How to spot it: For any deletion block in the diff, git grep <deleted function or string> across the whole repo (including docs/, scripts/, tests/, CI configs). Any hit means keep it.

2. Rename touched the wrong context

The agent renames user to currentUser in one component and accidentally rewrites a totally separate user.role somewhere else — another component, a type definition, a prop name.

How to spot it: Count the files where - user / + currentUser appears in the diff. If it’s outside the scope you named in the prompt, there’s bleed-over risk.

3. Logic changed without syntax changing

if (!user)if (user === null) flips behavior when user is undefined. array.find(x => x.id === id)array.filter(x => x.id === id)[0] differs when ids repeat. Promise.allPromise.allSettled turns “any failure throws” into “silent success.”

How to spot it: Read conditional, loop, and Promise combinator lines in the diff. These type-check fine but the semantics need line-by-line confirmation.

4. Added a new branch but didn’t update all callers

The agent added a parameter or return value but only updated some call sites. Strict TypeScript catches it; loose configs or plain JS won’t.

How to spot it: Function signature changed → grep -rn "functionName(" --include='*.ts' --include='*.tsx' and compare caller count to what the diff modified.

5. Import paths / export styles changed

export defaultexport const works in the two imports the agent updated, but misses lazy loads, dynamic imports, and Storybook config references.

How to spot it: grep -rn "from.*FileName" . covers all imports — diff them against what the AI updated.

6. Tests / mocks deleted to “make tests pass”

The most insidious: you said “make the test pass,” the agent deleted the test case or weakened the assertion.

How to spot it: Any test(, it(, expect(, or describe( line in the deletion column is a red flag.

Shortest path to fix

A fixed “risk-descending” reading order.

Step 1: Make the AI summarize its own diff

Before accepting, ask in chat:

Before I accept, summarize this change in the following format:

1. Files modified (full paths)
2. For each file:
   - Functions / behaviors deleted
   - Identifiers renamed
   - Behavior changes (not just syntax)
   - New dependencies / imports added
3. For every line you deleted, confirm whether it's referenced
   anywhere else in the repo
4. What is the single highest-risk change here, and why

If the summary mentions anything you didn’t expect, reject and ask the agent to refine or split the diff.

Step 2: Read deletions first, with git tooling

# Only show deleted lines
git diff --staged | grep '^-' | grep -v '^---'

# Which files have the most deletions
git diff --staged --stat

# Read each file, deletions side first
git diff --staged path/to/file.ts | less

In GitHub / Cursor’s diff viewer, switch to “Split” view and scan the red column top to bottom.

Step 3: Cross-grep every rename

For each - oldName / + newName pair in the diff:

# Where does the old name still appear
git grep -n "oldName"

# Compare against files the diff actually touched
git diff --staged --name-only

Mismatch = bleed-over risk. For ambiguous names (user, data, config), force the agent to pick a more specific identifier to avoid collisions.

Step 4: Apply “can I explain this in 5 minutes” to additions

Read new code last. For each function, ask:

  • What are the input boundaries? How does it handle null / undefined / empty arrays?
  • Any async race? Possible race conditions?
  • How does it propagate errors? Are any swallowed?

For anything you can’t explain quickly, ask the agent to add 5 lines of comments; for anything the agent can’t explain, demand a rewrite.

Step 5: Run two rounds of regression

Don’t just run the tests directly related to the change:

# Change-related tests
npm test -- path/to/changed.test.ts

# Full suite
npm test

# Then typecheck and lint to surface side effects you missed
npm run typecheck
npm run lint

If possible, run two worktrees — HEAD and the change — through the same end-to-end scenario and diff the outputs.

Risk typeToolPass criterion
Deleted useful codegit grep across repo0 remaining references
Rename bleed-overgit diff --name-only vs grepModified files = grep hit files
Semantic changeUnit tests + boundary casesBoundary case tests exist and pass
Untouched callerstypecheck + lintNo errors under strict mode

Prevention

  • Any AI diff over 200 lines gets a real review — never “Accept All”; if it’s over 500 lines, ask the agent to split into multiple commits
  • In CLAUDE.md / .cursorrules hard-code: “Before deleting any code, search the whole repo for references and report them”
  • git commit a checkpoint before any AI change so the diff is reviewable and rollback is one command
  • Strict TypeScript + strict ESLint are the safety net for AI coding — turning them off means coding naked
  • Add a pre-commit hook that blocks “test deletion”: git diff --cached | grep -E '^-\s*(test|it|expect)\(' && exit 1
  • Put critical modules (auth, payment, permission) in dedicated directories and use CODEOWNERS to require human review before merge

Tags: #AI coding #Debug #Troubleshooting