~/engineering-practices/how-to-debug-ai-generated-code.md

How to Debug AI-Generated Code: Understand First, Fix Last

5 min readNovember 04, 2026by Gorav Singal#debugging#ai#code-review
// TL;DR

Debugging AI-generated code is harder than debugging your own, for one simple reason: you have no mental model of code you never wrote. The bug is hiding inside confident, plausible-looking lines, and when you debug your own code half the work is already done because you remember writing it. So the real first step with AI code is not 'ask the AI to fix it' — it is to slow down and READ the code until you understand what it is doing. The method is classic, done honestly: (1) REPRODUCE the bug reliably, (2) UNDERSTAND the code so you know what SHOULD happen — the step everyone skips with AI code, (3) form ONE clear HYPOTHESIS, (4) ISOLATE it (prints, logs, a real debugger, or git bisect), (5) FIX and VERIFY and add a TEST. You can use AI at every step, but YOU drive — give it the error plus context, ask for possible causes, ask it to explain its own code — never hand it the bug and blindly accept 'Fixed!'. Left to run loose on a bug, the model doubles down, changes unrelated code, and confidently explains things that are wrong. Treat every 'Fixed!' as a claim to verify.

How to Debug AI-Generated Code: Understand First, Fix Last

In the last article I wrote about reviewing AI code before it goes in. This one is about the moment after — when some AI-written code is already running and now it is misbehaving, and you have to find out why. And I will be honest: debugging AI code is a bit harder than debugging your own, in a way that took me a while to put my finger on.

Why it is harder: you have no mental model

Why debugging AI code is harder. A bug in your own code: you remember writing it, you already have a rough mental map, you can guess where it probably is, and the bug usually matches a mistake you know you tend to make. A bug in AI's code: it is foreign code with no memory of it, it looks correct and confident, you must first understand it fully, and the bug hides inside plausible lines that all seem reasonable. So the first step is not fixing — it is understanding. With AI code the mental model does not exist, so slow down and read it until you understand what it does, then hypothesise, then fix. Understand first, hypothesise second, fix last.

Think about how you debug your own code. Half the work is already done before you even start, because you have a mental model of it. You remember writing that function, you know roughly where things live, and the bug usually turns out to be a mistake of a kind you know you tend to make. You can jump almost straight to a likely spot.

Now AI code. That mental model simply does not exist. The code arrived whole, polished, confident — and the bug is sitting somewhere inside lines that all look perfectly reasonable. You cannot jump to a likely spot because you never built the map. This is exactly why “just paste the error and ask the AI to fix it” fails so often: you are debugging blind, and you cannot even judge whether the AI’s fix is correct, beacuse you do not understand the code it is fixing.

So the single most important shift is this: with AI code, understanding the code is the first real step, not an optional one. Read it like you would read a new teamate’s code before touching it. Understand first, hypothesise second, fix last.

The method

Once you accept that, the rest is just classic debugging done with discipline.

A debugging method for AI code, five steps. One, reproduce: make it fail reliably and small. Two, understand: read the code and build the mental model — the step people skip with AI code and the most important one. Three, hypothesis: guess the cause, one clear theory. Four, isolate: bisect and narrow, binary-search it. Five, fix and verify: prove it and add a test. The tools that make this fast: reproduce first because a bug you cannot trigger you cannot fix, understand so you know what should happen, form one hypothesis instead of changing ten things, isolate with prints, logs, a debugger or git bisect, then fix, verify and add a test. The AI helps at every step but you drive.

  1. Reproduce it. A bug you cannot trigger on demand, you cannot fix. Make it fail reliably, and as small as possible — one input, one path.
  2. Understand the code. The step above. Read the relevant part until you know what it is supposed to do. Only then can you spot where reality differs.
  3. Form one hypothesis. Resist the urge to change ten things at once. Pick the single most likely cause and test that.
  4. Isolate it. Comment things out, add prints or structured logs, use a real debugger and step through line by line, or — my favourite for “it worked yesterday” — git bisect to find the exact commit that introduced it. Narrow the space until the bug has nowhere to hide.
  5. Fix and verify — and add a test. Confirm the fix actually solves the reproduction, not just makes the symptom disappear. Then write a test so this bug can never silently return. This last part is not optional.

None of this is new. It is the same loop good engineers always used. The only change for AI code is that step 2 is now explicit and non-negotiable.

The AI bugs that catch you out

After doing this a while, you start to recognise the flavour of AI bugs. They tend to cluster:

  • The silent wrong assumption. The AI assumed your data looked a certain way — sorted, non-empty, a particular key present — and it is subtly not. The code runs fine until the one input that breaks the assumption.
  • The hallucinated call that no-ops. It called a method or passed a flag that does not quite do what it thinks, so the code runs without error but silently does nothing useful. These are nasty because there is no exception to follow.
  • Copied-but-not-adapted. A pattern lifted from a common example (a tutorial-shaped solution) that does not quite fit your situation — right shape, wrong details.
  • The off-by-one and the edge case. The classic. The happy path is flawless; the boundary is wrong.
  • Version mismatch. The AI used an API from a different version of a library than the one you actually have installed.

Knowing these flavours makes step 3 — forming a hypothesis — much faster, because you have a shortlist of usual suspects.

Use the AI to help — but you stay the detective

Here is the irony: the AI is genuinely useful for debugging its own code — but only if you keep it on a tight leash.

Using AI to debug without it leading you astray. DO: give it the error plus the relevant code, ask for two or three possible causes and then you pick, ask it to explain what its own code does, ask what would make this fail, and make small reviewed changes one at a time. DON'T: paste the bug and say fix it blindly, accept Fixed without reproducing, let it rewrite half the file at once, let it change unrelated code, or trust a confident explanation unchecked. Why just-ask-the-AI-to-fix-it backfires: it doubles down and rewrites while insisting it is fixed, it changes unrelated code creating a new mystery, and it explains bugs confidently even when wrong. Stay in charge: you form the hypothesis, you decide the change, and treat every Fixed as a claim to verify.

The DO list works because it keeps you in charge: give the model the error and the relevant code, ask it for two or three possible causes (and you decide which to chase), ask it to explain what a confusing function actually does, ask “what input would make this fail?“. Used like this it is a brilliant, fast assistant.

The DON’T list is where people get burnt. Hand a bug back to the AI with no direction — “it’s broken, fix it” — and three bad things tend to happen, every time:

  1. It doubles down — confidently rewrites code while insisting it is fixed, usually without ever reproducing the actual problem.
  2. It changes unrelated code — so now you have a brand new mystery stacked on top of the original one.
  3. It explains the bug with total confidence even when the explanation is wrong — and off you go down a rabbit hole chasing a cause that was never real.

So the rule is simple: you form the hypothesis, you decide the change, and the AI does the specific legwork you direct — generate a test, explain this function, suggest causes for this symptom. And treat every cheerful “Fixed!” as a claim to be verified, never a fact. You are the one who reproduces it, and you are the one who signs off.

Takeaways

  • Debugging AI code is harder because you have no mental model of code you didn’t write — the bug hides in plausible-looking lines.
  • So understand the code first — reading it is step one, not optional. Then reproduce, hypothesise, isolate, fix, verify, add a test.
  • Learn the flavours of AI bugs: silent wrong assumptions, hallucinated no-op calls, copied-but-not-adapted patterns, off-by-ones, version mismatches.
  • Use AI to help, but stay the detective: give it the error + context, ask for causes, make small verified changes. Never hand it the bug blindly — it doubles down, touches unrelated code, and explains wrong things confidently.
  • Every “Fixed!” is a claim to verify. You reproduce, you sign off, you own it.

This pairs with how to review AI-generated code. For the wider picture, see the future of software engineering and the playbook to stay valuable. And to sharpen the raw debugging muscle itself, the Observability & Production Debugging course is the best investment you can make.

Share

Related Posts

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

A few months back I approved a PR that was almost entirely AI-generated. It…

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

In the previous article I argued that AI is not killing software engineering…

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

Every few weeks somebody sends me a scary headline — “AI will replace all…

Pull Request Best Practices in the AI Era

Pull Request Best Practices in the AI Era

A pull request is where individual work becomes team work — and where most of…

Code Review Best Practices in the AI Era

Code Review Best Practices in the AI Era

Code review is the last line of defence before code becomes everyone’s problem…

Git Commit Best Practices in the AI Era

Git Commit Best Practices in the AI Era

A commit is the smallest unit of a project’s history, and it is written far more…

Latest Posts

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

In the previous article I argued that AI is not killing software engineering…

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

A few months back I approved a PR that was almost entirely AI-generated. It…

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

Every few weeks somebody sends me a scary headline — “AI will replace all…

The Transactional Outbox Pattern: Never Lose an Event Again

The Transactional Outbox Pattern: Never Lose an Event Again

This is one of those patterns that looks like nothing until you get burnt once…

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Here is a question I like to ask in interviews: your bank account shows a…

CQRS Pattern, Explained Simply: Split Your Writes From Your Reads

CQRS Pattern, Explained Simply: Split Your Writes From Your Reads

Most of us start our career with one model for everything. The same class, the…