How to Debug AI-Generated Code: A Systematic Approach

Dev.to / 4/19/2026

💬 OpinionSignals & Early TrendsIdeas & Deep AnalysisTools & Practical Usage

Key Points

  • AI coding tools often work well on the “happy path,” but many AI-generated projects (about 43% per cited research) still require real debugging to handle edge cases before production use.
  • Relying on AI as the “driver” to fix errors can create a debugging trap by patching symptoms, leaving the root cause untouched, and causing new failures elsewhere.
  • The article argues that AI should be used as an assistant within a controlled debugging process rather than outsourcing the thinking and decision-making.
  • It recommends a systematic 5-step debugging approach starting with reliably reproducing the bug by capturing exact trigger steps, inputs, expected/actual outputs, and error details.

The Debugging Trap With AI Code

You built something cool with Cursor or Bolt. It worked on the demo. Then a user tried it with a slightly different input — and it blew up. You paste the error into the AI, it confidently rewrites the function, and now a different thing is broken. Welcome to the debugging trap.

Research from GitClear and others suggests around 43% of AI-generated projects need real debugging work before they're production-ready. AI coding tools are good at the happy path — the flow you described in the prompt. They are bad at the unspoken edges: empty arrays, zero values, null users, timezone boundaries, race conditions.

And here's the trap: when something breaks, the natural move is to paste the error into the AI and say "fix this." The AI does what you asked. It patches the symptom. The bug moves somewhere else. You paste that error. Repeat. Pretty soon you have a codebase held together with duct tape and nobody — not you, not the AI — understands what's actually happening.

There's a better way. It isn't magic. It's the same debugging discipline engineers have used for decades, just applied intentionally to AI-authored code.

Why "Just Ask AI to Fix It" Doesn't Work

AI is a great debugging assistant, but a terrible debugging driver. Here's why:

  • AI doesn't know which parts are working. From its perspective, everything in the file is suspect. It will often "fix" code that was fine and leave the actual bug untouched.
  • Without a reproduction case, AI guesses. If you say "sometimes the total is wrong," the AI has no way to know when or why. It will generate plausible-looking code that addresses a plausible-sounding problem. Plausible is not correct.
  • Each "fix" can introduce new bugs. AI can't see your app's runtime behavior. It can't observe the actual values. It's writing code based on pattern-matching, not evidence.
  • The fix-depth problem. AI tends to fix symptoms, not causes. A wrong total? Add a check in the caller. A null crash? Wrap it in a try/catch. The real bug — upstream, where the bad data was born — stays alive and keeps spawning new symptoms.

The fix is to stop outsourcing the thinking part of debugging. Use AI as a tool inside a process you control.

The Systematic 5-Step Debugging Approach

Step 1: Reproduce Reliably

You cannot fix what you cannot reproduce. Before anything else, get the bug to happen on demand.

Write down:

  • The exact steps that trigger it
  • The exact input values
  • The expected output
  • The actual output (including error messages and stack traces)

Then try to shrink it. This is called a minimal reproduction case (MRC). Remove everything that isn't part of the bug. If the bug shows up when you submit a form with 20 fields, can you reproduce it with 3? With 1? The smaller the MRC, the faster every subsequent step goes.

If you can't reliably reproduce it, don't skip to "fix." Keep poking until you can. An intermittent bug you can't reproduce is a bug you cannot verify you've actually fixed.

Step 2: Isolate the Scope

Once you can reproduce, figure out where the bug lives. The answer is almost never "the whole codebase."

Techniques that work:

  • Binary search. Comment out half the code path. Does the bug still happen? If yes, it's in the remaining half. If no, it's in the commented half. Keep halving until you land on it.
  • Logs at boundaries. Put a console.log at the entry and exit of each function in the suspect path. Print the inputs and outputs. The bug is between the last log that looks right and the first log that looks wrong.
  • Git bisect. If it used to work and now doesn't, git bisect will pinpoint the commit that introduced the bug.
// Suspect path: submit -> validate -> calculateTotal -> persist
function handleSubmit(order) {
  console.log('[submit] input:', order);
  const valid = validate(order);
  console.log('[submit] validated:', valid);
  const total = calculateTotal(valid);
  console.log('[submit] total:', total);
  return persist(valid, total);
}

Boring? Yes. Effective? Extremely. Four logs will usually cut the search space in half.

Step 3: Form a Hypothesis (Not a Guess)

Once you've narrowed the scope, stop and think. Before you change any code, write down:

  • What do I think is happening? In one sentence.
  • What would prove me right? A specific log value, a specific state, a specific code path.
  • What would prove me wrong? Because you might be wrong, and you want to know fast.

This sounds fussy. It isn't. The difference between a hypothesis and a guess is that a hypothesis is falsifiable. "Maybe the discount is being applied twice" is a hypothesis — you can check it. "Something's wrong with the pricing" is a vibe, and vibes lead to vibe-fixes.

Step 4: Verify With Evidence, Not Vibes

Test the hypothesis against reality. Read the actual values. Don't assume, observe.

  • console.log the specific variable at the specific line
  • Drop a debugger; statement and step through in DevTools
  • Set a breakpoint in your editor's debugger

This also applies to AI output. If the AI says "the bug is that discount is undefined at this line," don't just trust it — console.log(discount) and see. AI is often close but not exactly right, and acting on a confident wrong diagnosis wastes more time than verifying it.

Trust but verify applies to AI the same way it applies to your own assumptions.

Step 5: Fix the Root, Not the Symptom

Once you've confirmed the hypothesis, resist the urge to patch the nearest visible symptom. Trace upstream.

If a function returned a wrong number, don't add if (total < 0) total = 0 in the caller. Ask: why did it return a wrong number? Where did the bad input come from? Fix it there.

A common anti-pattern: wrapping symptoms in if-statements and try/catches. It feels like progress because the error goes away. But the bad data is still flowing through your system; you've just muffled its scream. A month later, the same root cause shows up in a different shape.

How to Use AI Effectively During Debugging

AI belongs inside this process, not on top of it. Use it like a sharp tool with a specific job:

  • Give AI the MRC, not the whole file. A 10-line reproduction is easier for both of you to reason about than 400 lines of context.
  • Share the actual evidence. The error message, the stack trace, the input values, the expected vs actual output. Not "it doesn't work."
  • Ask AI to explain its hypothesis before writing code. "What do you think is happening, and why?" If the explanation doesn't match what you've observed, don't let it write the fix.
  • If the fix doesn't work, go back to Step 1. Don't ask AI to "try again." That's how you end up with ten rounds of whack-a-mole. If the fix failed, your hypothesis was wrong. Re-reproduce, re-isolate, re-hypothesize.

Also: good tests make debugging dramatically faster, because they tell you which parts of the code are already proven to work. Tests catch regressions while you debug, so you're not accidentally breaking other things while chasing the current bug.

A Real Debugging Example

Let's make this concrete. Here's a function an AI generated for calculating an order total with a discount:

// pricing.ts
export function calculateTotal(
  price: number,
  quantity: number,
  discountPercent: number
): number {
  const subtotal = price * quantity;
  const discount = subtotal * (discountPercent / 100);
  return subtotal - discount;
}

Your users are reporting that when they apply no discount, the total comes out as NaN. Let's walk through the 5 steps.

Step 1 — Reproduce. In a test or a REPL:

calculateTotal(100, 2, 0);    // expected: 200, actual: NaN
calculateTotal(100, 2, null); // NaN
calculateTotal(100, 2);       // NaN

Confirmed: it breaks when discountPercent is 0, null, or missing.

Step 2 — Isolate. Add logs:

console.log('price:', price, 'quantity:', quantity, 'discount%:', discountPercent);
const subtotal = price * quantity;
console.log('subtotal:', subtotal);
const discount = subtotal * (discountPercent / 100);
console.log('discount:', discount);

Output for the zero case shows subtotal: 200, discount: 0, total: 200. So the zero case is actually fine. It's the missing case that prints discount: NaN, because undefined / 100 is NaN.

Step 3 — Hypothesis. When discountPercent is undefined (not passed in), undefined / 100 produces NaN, which then contaminates subtotal - discount.

Step 4 — Verify. console.log(undefined / 100) in DevTools → NaN. Confirmed.

Step 5 — Fix the root. The root cause is that the function doesn't handle the "no discount" case. Don't patch the caller; fix the signature:

// pricing.ts
export function calculateTotal(
  price: number,
  quantity: number,
  discountPercent: number = 0
): number {
  const subtotal = price * quantity;
  const discount = subtotal * (discountPercent / 100);
  return subtotal - discount;
}

One character fixed the bug. But you wouldn't have known which character without the 5 steps. Notice how much more useful this is than pasting "my total is NaN, fix it" into the AI — which might have rewritten the whole function, added a try/catch, or coerced every input to a number just in case.

Debugging Tools Vibe Coders Should Know

A small toolkit goes a very long way:

  • Browser DevTools. Console, Network, Sources. Learn the Sources tab specifically — breakpoints, watch expressions, and the call stack will save you more time than any AI prompt.
  • Strategic console.log. Log at boundaries, print the variable name with the value (console.log('user:', user)), and clean up when you're done.
  • The debugger statement. Drop debugger; anywhere in your code and execution will pause there when DevTools is open. Step through line by line.
  • Error tracking (Sentry free tier). Captures errors from real users in production with full stack traces and the state at the moment of failure. You stop debugging in the dark.

Honest truth: for a lot of everyday bugs, a developer who knows DevTools is faster than any AI. The AI has to reason about possibilities. You can look at the actual value.

When Debugging Reveals Architectural Problems

Sometimes the problem isn't the bug in front of you. It's the shape of the code around it.

Watch for these signals:

  • You keep fixing bugs in the same file or module
  • Every fix breaks two other things
  • You can't explain how the data flows from input to output
  • The same logic is duplicated in three places, and you have to fix each one

When this happens, debugging is telling you something bigger: the code is tightly coupled, the abstractions are missing, the data flow is unclear. These are signs that refactoring — not more patches — is the real fix.

And if every bug fix seems to break something else, you might be past the point where you can debug your way out of it. That's when it's time to bring in help to stabilize the codebase before you keep shipping on top of it.

Closing

Debugging is a skill, not a magic AI trick. Each bug you debug properly makes you measurably better at the next one — you build intuition about where bugs hide, which tools to reach for, and which AI fixes to trust.

AI accelerates debugging when you use it inside a real process. It replaces debugging when you use it instead of one. The difference is whether you're in control, or whether you're stuck in a loop asking "fix it" until the code is unrecognizable and the bug is still there.

Next time something breaks: reproduce, isolate, hypothesize, verify, fix the root. In that order. Every time.

Originally published at bivecode.com. If you're struggling with an AI-built codebase that's become unmanageable, BiveCode Rescue can help.