To debug code with AI, give the agent a way to reproduce the bug, the full error and logs, and a clear rule: find the root cause before changing anything. A coding agent such as Claude Code or Codex can run your tests, read stack traces, bisect your git history and add logging, which makes it a strong debugging partner. It fails in predictable ways too: it guesses when it cannot run the failing case, and it “fixes” symptoms by catching errors or relaxing tests.
This guide is a workflow that plays to the first and guards against the second: reproduce, feed the evidence, narrow down, explain, fix, lock it in with a test, and know when to pull the plug.
Why agents get debugging wrong
Most bad AI fixes come from the same few places:
- No reproduction. The agent reads the code, forms a theory and patches it. Nothing proves the theory was right.
- Partial evidence. You paste the last line of the error. The useful frame, three levels down the stack, never reaches the model.
- Fixing the symptom. A null check, a
try/catchor a retry makes the error disappear while the bad state that caused it is still there. - Moving the goalposts. The test fails, so the agent edits the test.
- Long, polluted sessions. After five failed attempts the conversation is full of wrong theories, and the model keeps building on them.
Every step below is aimed at one of these.
Step 1: reproduce the bug first
Before the agent touches code, it needs a command that shows the bug. That can be a failing test, a curl call, a script, or a page in the browser. The point is that anyone, including the agent, can run it and see the failure.
If you already have one, put it at the top of the prompt:
This fails:
pnpm vitest run src/billing/invoice.test.ts -t "applies discount once"
Expected total 90, got 81. Don't change any code yet.
Run the test, confirm you see the same failure, and tell me what you see.
If you don’t have a reproduction, make that the first task:
Users report that saving a draft twice creates two drafts.
Write a failing test that reproduces this. Do not fix anything.
Stop when the test fails for the right reason.
“For the right reason” matters. A test that fails because of a typo in the test proves nothing. Ask the agent to show the failure output and explain why it matches the bug report. For intermittent bugs, have it run the reproduction in a loop (for example 50 times) and report how often it fails, so you can tell later whether a fix actually helped.
Step 2: give the agent the full evidence
Agents are good at reading long, noisy output. Don’t trim it for them. Give:
- the complete stack trace, not only the message;
- the exact command and its full output;
- relevant logs around the time of the failure;
- what changed recently: a dependency bump, a deploy, a merged branch;
- environment details if they might matter: OS, runtime version, feature flags.
You don’t have to paste everything into the chat. Save it to a file and point the agent at it, or pipe it straight in. With Claude Code’s print mode:
pnpm test 2>&1 | tail -n 200 > /tmp/test-output.txt
cat /tmp/test-output.txt | claude -p "Explain the root cause of this failure. Don't edit files."
Inside an interactive session, ! runs a shell command and puts its output into the conversation in both Claude Code and Codex, for example !pnpm test src/billing. For huge logs, ask the agent to grep for the request ID or the error code instead of reading the whole file; it saves a lot of the context window.
Step 3: narrow it down with git bisect
When something used to work and now doesn’t, you don’t need a theory. git bisect finds the first bad commit by binary search, and it can run fully automatically, which makes it a perfect job for an agent.
First, write a small script that exits 0 when the code is good and non-zero when it is bad:
#!/bin/sh
# check.sh
pnpm install --frozen-lockfile >/dev/null 2>&1 || exit 125
pnpm vitest run src/billing/invoice.test.ts -t "applies discount once"
Then let git do the walking:
git bisect start
git bisect bad HEAD
git bisect good v2.3.0
git bisect run ./check.sh
git bisect reset
The exit codes are the contract, as the official git bisect docs define them: 0 means good, any code from 1 to 127 except 125 means bad, 125 means “can’t test this commit, skip it”, and anything higher aborts the bisect. That is why the install step above exits 125: a commit that doesn’t even install should be skipped, not counted as bad.
A prompt that hands the whole thing to an agent:
The invoice discount test passes on tag v2.3.0 and fails on main.
Write a check script for git bisect run (exit 125 if the commit
can't be built), run the bisect, then git bisect reset.
Report the first bad commit and explain which line in its diff
causes the failure. Don't fix anything yet.
Make sure the working tree is clean first, or do it in a separate git worktree so the bisect checkouts don’t fight with your own work. If the test file itself didn’t exist in old commits, copy it to a path outside the repo and have the script run that copy.
Step 4: add logging, then remove it
When the stack trace doesn’t explain the state that led to the error, the agent needs to see values at runtime. Ask for targeted, temporary logging:
Add temporary logs that print the cart items, the discount list and
the running total at each step of applyDiscounts(). Prefix every line
with [DEBUG-1234] so we can find and remove them later. Run the
failing test and show me the log lines.
The prefix is the trick. When the bug is understood, one instruction cleans up:
git grep -n "DEBUG-1234"
Agents also handle debuggers well when they are scriptable: node --inspect with a breakpoint is awkward for a terminal agent, but a quick python -m pdb session, an extra assertion, or a one-off script that calls the function with the failing input usually gets the same answer faster.
Step 5: root cause before the fix
This is the most important rule. Ask the agent to explain the bug before it writes a fix, and read the explanation:
Before changing any code, explain the root cause:
1. What state is wrong, and where does it first become wrong?
2. Why does the code produce that state?
3. Why didn't existing tests catch it?
Then propose the smallest fix and wait for my OK.
A good answer points at a specific line and a specific condition (“the discount is applied in both addItem and checkout since commit a1b2c3 moved the call”). A weak answer is vague (“there may be an issue with how discounts are handled”) or jumps to a defensive patch. If you get the weak kind, the agent hasn’t found the cause yet; send it back to Step 3 or 4.
Claude Code’s plan mode is useful here, because the agent can read and run read-only commands but not edit files until you approve. Start it with claude --permission-mode plan, or press Shift+Tab to cycle into it. Claude Code plan mode covers it in detail. In Codex, /plan serves a similar purpose.
Step 6: fix it and lock it in with a regression test
Now let the agent fix it, with constraints:
Apply the fix. Rules:
- Do not edit or skip existing tests.
- Do not catch and swallow the error.
- Add a regression test named after the bug that fails without
the fix and passes with it. Prove both: revert the fix, run the
test, restore the fix, run it again.
- Run the full test suite at the end.
The “prove both” part is what makes the test trustworthy. A regression test that would have passed before the fix is decoration. For more on getting useful tests out of an agent, see how to write unit tests with AI.
Then review the diff yourself, looking for the usual symptom fixes:
| Pattern in the diff | Why it’s suspicious |
|---|---|
New try/catch that logs and continues | The error is hidden, the bad state remains |
Optional chaining or ?? default added at the crash site | Treats missing data as normal instead of asking why it’s missing |
| Retries or longer timeouts | Often masks a race condition |
Type widened to any or unknown | Silences the compiler instead of fixing the type |
| Test expectation changed | The goalpost moved |
Sometimes one of these is the right fix. The question to ask is whether the agent explained why.
Browser bugs: give the agent a browser
UI bugs are hard to describe in text: a button that does nothing, a layout that breaks after a click, an error that only shows in the console. With Playwright MCP the agent can open the page and look for itself. Add it to Claude Code:
claude mcp add playwright npx @playwright/mcp@latest
or to Codex in ~/.codex/config.toml:
[mcp_servers.playwright]
command = "npx"
args = ["@playwright/mcp@latest"]
Tools such as browser_navigate, browser_click, browser_snapshot, browser_console_messages and browser_network_requests let the agent reproduce the bug step by step, read the console errors and see which API call failed. A debugging prompt:
Start the dev server. Open http://localhost:5173/settings, change the
display name and click Save. Report console errors and the network
request for the save, with status and response body. Don't fix yet.
Once the cause is clear, ask the agent to turn the steps into a Playwright test so the bug stays fixed. AI agent browser testing goes deeper into the setup.
Bugs that only show in CI
When tests pass locally but fail in CI, the difference is usually environment: timezone, locale, parallelism, missing env vars, a different Node or Python version, or test order. Give the agent the CI log and the workflow file, and ask it to list the differences between CI and your machine before guessing. Running the suite in random order or with the CI’s timezone (TZ=UTC pnpm test) often reproduces the failure locally. If CI failures are a regular chore, auto-fixing CI failures with AI shows how to automate the loop.
When to stop the agent
An agent will keep trying long after it should stop. Pull the plug when:
- Two or three fixes have failed. Each new attempt is built on the same wrong theory.
- The diff spreads. It started in
invoice.tsand is now editing config, unrelated modules or lock files. - It touches the tests. Changing an assertion to match the wrong output is never a bug fix.
- It can’t reproduce the bug but fixes it anyway.
- The explanation keeps changing between turns without new evidence.
What to do instead:
- Stop the turn (Esc in Claude Code and Codex).
- Undo its changes. Commit before you start debugging so
git restore .orgit stashtakes you back; undoing Claude Code changes covers the options. - Write down what you learned: which theories were wrong and why.
- Start a fresh session with the reproduction, the facts and the ruled-out theories. A clean context with three good facts beats a long one with ten bad guesses.
Sometimes the right move is to take a narrower question: “Is cart.items already wrong when checkout() is called?” is easier to answer than “fix the checkout bug”.
Debugging with AgentVera
AgentVera is a desktop app for macOS, Windows and Linux that runs Claude Code, Codex, Gemini CLI and other CLI agents side by side in real terminals. Several of its tools fit the workflow above:
- Send to agent: select an error in the terminal dock, the browser console or Docker logs and send it to an agent with ⌘⇧L, or click the Fix badge on a caught error. Long logs are capped at 12,000 characters and keep their end, where the stack trace usually is.
- Agent browser: a separate-profile Chrome that agents drive through Playwright MCP, with tabs streamed live so you can watch the agent reproduce the bug.
- Checkpoints: each turn is snapshotted to a hidden git ref, so you can see what a failed attempt changed and undo that turn without touching your branches or stash.
- Two theories in parallel: start two agents in separate worktrees from the Git panel, give each one a different hypothesis, and keep the fix that holds up.
- Verify: on Pro and Team, criteria like “the regression test fails without the fix and passes with it” are checked by a separate read-only verifier when the agent’s turn ends.
Once the bug is fixed, write down what you learned where the next person will find it; writing documentation with AI covers that side.
Wrapping up
Debugging with an AI agent works when you treat it like debugging with a sharp colleague: start from a reproduction, share all the evidence, narrow the search with git bisect, add logging to see real values, and insist on the root cause before the fix. End every bug with a regression test that you have seen fail and pass. And when the agent starts guessing, stop it, undo, and start fresh with what you’ve learned.