When an agent does something wrong, you debug it the same way you debug any program with one extra step: first you find out what it did. Read the trace of its steps and tool calls, reproduce the bad run, then fix the single weak step instead of rewriting the whole prompt.
Why do agents need a different debugging loop?
A normal function is deterministic: same input, same output, so a stack trace tells the whole story. An agent is not. The model decides what to do at each step, so the same task can take a different path twice. That means "it broke once" is not a bug report yet. You need the trace of what happened, and a way to make it happen again.
So the loop has four moves: observe what the agent did, reproduce it reliably, isolate the one step that went wrong, then change that step and re-run to confirm. Most of the work is the first two. Once you can see the run and repeat it, the fix is usually small.
Step 1: observe what the agent did
You cannot fix a step you cannot see. An agent run is a sequence of turns: the model thinks, calls a tool, reads the result, and decides again. Your first job is to read that sequence in order.
The Claude Code CLI records every session, and you can replay one. Resume the most recent run in the current directory, or pick a past session from a list:
# Continue the most recent session here
claude --continue
# Pick from a list of past sessions, or resume one by id or name
claude --resume
claude --resume auth-refactorTo read a run as data instead of scrolling the terminal, ask for verbose, turn-by-turn output. This shows each tool call and each tool result, which is exactly the part you need:
claude --verboseFor a one-shot run you want to capture and inspect later, use print mode with a
structured output format. stream-json emits one JSON event per line as the
agent works, so you can save it and grep through it:
# Emit one JSON event per line; --verbose is required for stream-json
claude -p "Summarize the failing tests" \
--output-format stream-json --verbose > run.jsonlEach line in that file is one event: a user message, an assistant message, a tool call, a tool result, or a system event. Reading them in order is the trace.Common gotchaThese saved trace and debug files capture raw tool results, so they often hold API keys, tokens, or file contents the agent read.Keep them out of git and delete them when you are done, do not paste one into a shared issue.
What should I look for in the trace?
In the trace, you are looking for the first step that goes wrong, not the final error. Watch for these:
| Symptom in the trace | What it usually means |
|---|---|
| Tool called with bad arguments | The tool's description or schema is unclear |
| Agent never calls a tool it should | The tool is missing, misnamed, or not allowed |
| Tool returns an error, agent retries blindly | The result is not telling the model how to recover |
| Agent loops on the same action | No stopping condition, or it cannot tell it succeeded |
| Right tools, wrong conclusion | The task framing or context is the problem |
The last column is the real bug. The visible error is often three steps downstream of it.
Turn on debug logs for the plumbing
If the problem is lower down, in tool wiring or an MCP connection, raise the log level. Debug mode prints internal events, and you can filter to the categories you care about so the output stays readable:
# Show API and MCP internals only
claude --debug "api,mcp"
# Write debug output to a file for a long run
claude --debug-file /tmp/agent-debug.logIf the failure is an MCP server that will not connect at all, that is its own fix path: see fix an MCP server that won't connect.
Step 2: reproduce the bad run
A bug you cannot repeat is a bug you cannot fix, and with a non-deterministic system that is the hard part. You reduce the randomness by pinning everything around the model.
Pin the inputs. Save the exact prompt and the exact files or data the agent
saw. If the agent reads src/auth/session.ts, snapshot that file at the version
it failed on. A run that reads different inputs is a different run.
Save the prompt as a fixture. Keep the failing prompt verbatim so you can fire it again unchanged. A reusable, copy-paste prompt looks like this:
Re-run in isolation. Run the repro in a clean, separate checkout so a fix experiment cannot corrupt your main working tree. A git worktree gives you a fresh directory on its own branch from the same repo:
# Create an isolated checkout on a new branch
git worktree add ../agent-repro -b agent-repro
# See all worktrees you have open
git worktree listNow you can run the agent against ../agent-repro and throw it away afterward.
For more on this pattern, see isolate parallel agents with git worktrees.
Accept that one run is not proof. Because the path varies, run the repro a few times. If it fails three times out of five, you have a real reproduction. If it fails once in twenty, you are chasing a rare path and should still pin more inputs before you trust any fix.
Step 3: isolate the weak step
Now narrow from "the run failed" to "this step failed". Walk the trace forward and stop at the first step whose result is wrong. Everything after that is the agent reacting to a bad result, so ignore it for now.
Once you find the step, sort the cause into one of three buckets. The bucket tells you what to change.
Is it the tool?
The model called the right tool but got a bad result, or could not call it at all. Check the tool's definition. A tool the agent uses well has a clear name, a one-line description of when to use it, and a typed schema for its arguments. Here is a tool described as config, the way the agent sees it:
{
"name": "get_order_total",
"description": "Return the final total for an order, including any coupon. Use this before quoting a price to the customer.",
"input_schema": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "The order id, e.g. ord_123" },
"couponCode": { "type": "string", "description": "Optional coupon code" }
},
"required": ["orderId"]
}
}If the agent passed a coupon in the wrong field, the schema or its descriptions were not specific enough. Tighten the wording, mark fields required, and the agent calls it correctly. For the underlying mechanic, see tool calling explained.
Is it the context?
The agent had the right tools but the wrong understanding. It was missing a file, given stale data, or buried under so much input that it lost the point. The fix is what goes into the run, not the code: feed it the exact file, drop the noise, and state the goal plainly. This is its own skill, covered in feed Claude the right context and context engineering for AI agents.
Is it the instruction?
The tools and context were fine, but the task was vague, so the model guessed. A prompt like "fix the checkout" leaves too much open. Rewrite it to name the file, the expected behaviour, and the stopping condition. The repair pattern lives in fix a prompt that isn't working.
Step 4: fix one thing and re-run
Change exactly one thing, then run the pinned repro again. If you change the tool schema and the prompt and the context at once, a passing run tells you nothing about which change worked, and a still-failing run tells you even less. One change per loop keeps the signal clean.
Keep the change tied to the bucket you found:
- Tool bucket: sharpen the description, make the schema stricter, or make the tool return a clearer message on failure so the agent can recover.
- Context bucket: add the missing file, remove the irrelevant input, or state the goal at the top of the prompt.
- Instruction bucket: name the target file, the success condition, and what not to touch.
Then run the saved repro again, several times, and compare against the bucket of runs from step 2. You are not looking for one lucky pass. You want the failure rate to drop close to zero across repeated runs.Common gotchaThe trap is declaring victory on the first green run, because a non-deterministic agent can pass once for the same random reason it sometimes failed.One pass moves your evidence from "fails most runs" to "unknown", not to "fixed". Re-run the pinned repro the same number of times you ran it in step 2 and compare failure rates before you trust the change.
Add guardrails so the fix sticks
A debugged agent regresses the moment the prompt drifts or a tool changes. Two habits keep it fixed.
Turn the repro into a check. Keep the failing prompt and its pinned inputs as a saved eval, and run it before you ship any change to the agent. It is a regression test for a non-deterministic system. The bug you fixed becomes the test that proves it stays fixed.
Gate risky actions with hooks. A hook can block or confirm a dangerous tool call before it happens, so a bad step is caught at runtime instead of in a post-mortem. See hooks and guardrails.
The whole loop in one pass
Put together, debugging an agent is short once you can see the run:
- Observe. Replay the session with
--verbose, or capture--output-format stream-jsonto a file, and read the trace in order. - Reproduce. Pin the prompt and inputs, run in an isolated git worktree, and confirm it fails on most runs, not just one.
- Isolate. Find the first wrong step and sort it into tool, context, or instruction.
- Fix. Change one thing, re-run the pinned repro, and watch the failure rate drop.
- Lock it in. Save the repro as an eval and add a hook for anything risky.
The shift from normal debugging is small but it is the whole game: you are debugging a process that does not repeat itself, so you spend your effort making it repeat before you try to fix it. Once you can see the run and reproduce it, the fix is almost always one clear, checkable change.