Debug with AI without going in circles
Debugging with an AI assistant fails the same way every time: you paste an error, it guesses, you paste the new error, it guesses again, and forty minutes later you have three "fixes" layered on top of a bug you still don't understand. The problem is never the model — it's that you handed it a symptom and asked for a cure. This guide teaches you to run the loop like an engineer, so the AI amplifies your diagnosis instead of replacing it with confident nonsense.
The circle happens because the AI is optimizing for a plausible next message, not a fixed bug
When you paste 'TypeError: cannot read property id of undefined' and nothing else, the model has no way to know which of forty call sites produced it. It picks the statistically likely one, writes a null check, and moves on. That patch often makes the error move rather than disappear — now it's undefined two functions upstream — and you're in the circle. The mental reframe that fixes this: the AI is not a debugger, it's a very fast hypothesis generator that has never seen your running program. Your job is to feed it observations it can't get on its own — actual runtime values, the real stack, the git diff since it last worked — so its next guess is constrained by reality instead of by the average of every Stack Overflow post.
Reproduce before you explain — a bug you can't trigger on demand cannot be debugged with AI
The single highest-leverage move is getting a deterministic reproduction before you type a word to the assistant. Intermittent bugs turn the AI into a slot machine: every 'fix' looks like it worked because the bug just didn't happen to fire that run. Nail down the exact inputs, the exact route, the exact click sequence, and confirm it fails 5 times out of 5. Then hand the assistant the reproduction itself, not your description of it — a failing test, a curl command, a Playwright script. 'Here's a test that fails, make it pass without changing the assertion' is a fundamentally different request than 'why is this broken,' because now the model has a binary oracle it can reason against and you have a way to catch it lying.
// Give the AI this, not a paragraph. It's a reproduction AND a regression guard.
test('checkout fails when cart has a deleted product', async () => {
const cart = await seedCart({ items: [{ productId: 'deleted-123' }] })
const res = await checkout(cart.id) // throws today
expect(res.status).toBe('completed')
})Give it the whole crime scene: stack trace, the code, AND the actual values
The most common failure mode is pasting the error message and stopping. An error message is the last domino; the stack trace tells you which dominoes fell and in what order. Paste the full trace, then the source of the top 2-3 frames that live in your own code (skip node_modules frames — the bug is almost never there), and critically, the runtime values at the failure point. Add a log line or a breakpoint and tell the AI what the variables actually contained: 'user is {id: 42, org: null}, and line 88 does user.org.plan.' That last part collapses the hypothesis space from twenty candidates to one. A senior engineer debugging over your shoulder would ask 'what's in that variable?' before touching anything — front-run that question every time.
Make it diagnose out loud before it's allowed to touch code
Explicitly forbid the fix on the first turn. Prompt it with: 'Do not write any code yet. Give me the three most likely root causes ranked by probability, and for each one tell me the single log line or check that would confirm or rule it out.' This does two things. It surfaces the model's actual reasoning so you can catch a bad assumption before it becomes ten lines of committed code, and it converts debugging from a guessing game into a series of cheap experiments. You run one check, report the result, and half the hypotheses die instantly. This is the difference between binary search and brute force — and it's the technique that most reliably breaks the circle, because each turn provably removes possibilities instead of adding untested patches.
When you're truly lost, bisect — with git or with print statements
Sometimes no one, human or model, can eyeball the cause. That's not a failure; it's a signal to switch from reasoning to bisection. If it worked yesterday and doesn't today, `git bisect` will find the exact commit that broke it in log-of-n steps, and 'this commit introduced the bug, here's the diff, explain how' is the most tractable debugging prompt that exists — you've handed the AI the murder weapon. If it's never worked, bisect the data flow instead: log the value at the entry point, at the midpoint, at the exit. The AI is excellent at telling you where to place probes and terrible at guessing which of them will light up, so use it for the former and your own eyes for the latter.
git bisect start
git bisect bad # current HEAD is broken
git bisect good v1.4.0 # this tag worked
# git checks out the midpoint; you test, then mark good/bad
# repeat until it prints the first bad commit, then hand that diff to the AITrust the runtime over the AI's story — and over your own
The AI will tell you a clean, confident narrative about why the bug happens. So will your own intuition. Both are theories; the running program is the only witness. When the model says 'the request is failing because the auth token is expired,' don't nod — log the token and the response status. Half the time the token is fine and the real problem is a trailing slash in the URL or a CORS preflight the model never considered because it couldn't see the network tab. Cultivate active distrust: every claim about what the code is doing gets verified with an observation before you build on it. This one habit is what separates engineers who debug in minutes from those who thrash for hours, and it matters more, not less, when a tireless machine is generating plausible stories faster than you can check them.
Kill 'fix layering' — revert failed attempts before trying the next one
Here's the subtle trap that quietly poisons AI debugging sessions: each failed fix leaves residue. The null check that didn't work, the try/catch that swallowed the real error, the 'let me add a fallback' that now masks the symptom. Three turns in, your code is a sediment of half-theories, and now the AI is debugging a mess partly of its own making. Discipline: if a fix doesn't work, `git checkout` that change before you try the next hypothesis. Debug against a clean baseline every time. Commit only the fix that actually resolves the reproduction. A stash or a scratch branch costs nothing and keeps every experiment isolated, so a failed attempt teaches you something instead of adding a new variable.
Close the loop: confirm the mechanism, then write the regression test
A bug isn't fixed when the error stops — it's fixed when you can explain the mechanism in one sentence and the reproduction now passes. If the AI's patch makes the symptom vanish but neither of you can say why, you don't have a fix, you have a coincidence that will resurface under slightly different inputs. Make the model articulate the causal chain: 'the webhook arrived before the DB transaction committed, so the read saw stale state.' Then turn your reproduction into a permanent test so the exact bug can never silently return — this is also the cheapest insurance against the AI reintroducing it in a future refactor. The test you wrote in step two to catch the bug becomes the test that guards the fix forever.
Takeaways
- →The AI is a hypothesis generator, not a debugger — your job is to feed it observations it can't get on its own.
- →Get a deterministic reproduction before you type anything to the assistant; a bug you can't trigger on demand can't be debugged.
- →Forbid the fix on turn one: demand ranked root causes plus the cheapest check to confirm or kill each.
- →Paste the full stack trace and the actual runtime values, not just the error message.
- →When reasoning fails, bisect — git bisect for regressions, print statements for data flow.
- →Revert every failed fix before trying the next, and don't call it fixed until you can state the mechanism in one sentence.