# How to Debug With Claude Code (The Full Workflow) > **Quick answer:** Give Claude Code a way to verify the fix (a failing test, a build, a screenshot), describe the symptom and reproduction steps instead of saying 'fix the bug,' and use plan mode for anything touching multiple files. Delegate large investigations to a subagent, checkpoint before risky changes, and run /doctor or --safe-mode when Claude Code itself is the thing misbehaving. *Published by [TryUncle](https://tryuncle.com) — the on-screen assistant that teaches DaVinci Resolve on your own screen.* *Updated 2026-08-01 · Claude Code documentation (August 2026) · Canonical: https://tryuncle.com/learn/claude-code/how-to-debug-with-claude-code* I've watched Claude Code find a race condition in about four minutes that would have taken me an afternoon of print statements. I've also watched it confidently tell me a bug was fixed when it wasn't, because nobody, human or model, had actually run anything to check. Both of those are true at once, and the gap between them is what this guide is about. We've spent 7+ years doing professional, commercial video editing, and these days a meaningful share of that work runs through a terminal: Fusion scripts, render-queue automation, batch exports scripted against DaVinci Resolve's own API. Debugging with Claude Code isn't a separate skill from editing anymore for a lot of us. It's the same muscle, pointed at a different kind of bug. ## What does it actually mean to "debug with Claude Code"? Debugging with Claude Code means giving the model a bug report it can act on, a way to verify its own fix, and a scope narrow enough that it doesn't burn your context window reading half the codebase before it starts. Claude Code can read files, run your test suite, and iterate on a fix autonomously, but it stops the moment the work "looks done," not the moment it's actually correct. That distinction matters more than any specific command. **Traditional debugging follows a hypothesis, instrument, reproduce, inspect, fix loop that hasn't fundamentally changed since programmers started using debuggers**, and Claude Code doesn't replace that loop. It compresses it. A cycle that used to take you twenty minutes of adding print statements, rerunning, and reading output now takes Claude Code under a minute, because it can read the stack trace, form a hypothesis, add instrumentation, rerun the failing command, and read the result without you typing any of the intermediate steps yourself. The loop is the same. The latency inside it collapsed. What changes practically is where you spend your attention. You're not writing the print statements anymore. You're writing the bug report that tells Claude where to start, deciding when to let it run unsupervised versus when to review each step, and checking the evidence it comes back with instead of the code it wrote to get there. ## What's the actual debugging loop, and why does "looks done" fail so often? Claude Code stops when its own judgment says the work is finished, and without an external check, that judgment is the only signal in the room. Claude Code's own best-practices documentation frames this directly: *"Claude stops when the work looks done. Without a check it can run, 'looks done' is the only signal available, and you become the verification loop: every mistake waits for you to notice it."* That's the trust-then-verify gap in one sentence, and it's the single most common way an AI-assisted debugging session goes wrong. Claude produces a plausible-looking fix. The code compiles. The change addresses the symptom you described. And it still doesn't handle the actual edge case that was breaking production, because nothing in the session forced that edge case to get exercised. **Give Claude something that returns a pass or fail, and the debugging loop closes on its own instead of depending on you to notice what it missed.** A failing test, a build's exit code, a linter, a script that diffs output against a fixture, or a screenshot compared against a design mockup all work. The check doesn't need to be sophisticated. It needs to exist. Here's the practical shift, straight from Claude Code's own guidance on the difference between a vague ask and a verifiable one: | Situation | Vague prompt | Verifiable prompt | | --- | --- | --- | | Bug report | "fix the login bug" | "users report login fails after session timeout, check the auth flow in src/auth/, especially token refresh. Write a failing test that reproduces the issue, then fix it" | | Build failure | "the build is failing" | "the build fails with this error: [paste error]. Fix it and verify the build succeeds. Address the root cause, don't suppress the error" | | UI regression | "make the dashboard look better" | "[paste screenshot] implement this design. Take a screenshot of the result and compare it to the original. List differences and fix them" | **A bug report without a reproduction step is a request for a guess, not a fix.** Notice what the right-hand column adds every time: a concrete symptom, a location to start looking, and a way to know when it's actually done. That third part is the one people skip, and it's the one that turns "looks done" into "verifiably done." ### Three ways to gate the stop, not just one You don't have to ask for verification fresh in every prompt. Claude Code's documentation lays out three tiers, from lightest to strongest: 1. **In one prompt.** Ask Claude to run the check and iterate in the same message you send the bug report. This works for any single debugging session today, with zero setup. 2. **Across a session, with a `/goal` condition.** A separate evaluator re-checks the condition after every turn, and Claude keeps working until it holds. Useful when you're stepping away and want the session to keep iterating without you babysitting each turn. 3. **As a deterministic gate, with a Stop hook.** A hook runs your check as a script and blocks the turn from ending until it passes. Claude Code overrides the hook and ends the turn after 8 consecutive blocks, so a genuinely unfixable check can't trap the session in an infinite loop. Each tier trades setup time for how much attention it needs from you. The prompt version costs nothing and works right now. The Stop hook version is what lets an unattended debugging run finish correctly while you're doing something else entirely, which matters if you're the kind of person running Claude Code in one terminal tab while color grading in another. ## How do you write a bug report Claude Code can actually use? A useful bug report names the symptom, points at where to look, and states what "fixed" means, all in the first message, so Claude doesn't spend its first several tool calls guessing at scope. "Fix the login bug" gives Claude nothing to anchor on except the word "login," so it starts reading broadly: the auth file, whatever it imports, the middleware, the tests. Four files in, before Claude has said anything back to you, you've already burned several thousand tokens of context on a search that a two-sentence prompt would have made unnecessary. Compare that to: "users report login fails after session timeout, check the auth flow in src/auth/, especially token refresh. Write a failing test that reproduces the issue, then fix it." Same bug, radically different session. Claude knows where to look, knows the specific mechanism to suspect, and has an explicit instruction to prove the bug exists before touching a line of the fix. Three details consistently separate a bug report that gets fixed in one pass from one that takes four: **The reproduction command.** If you can trigger the bug with a specific test, script, or CLI invocation, give Claude that exact command. "It's slow sometimes" is unusable. "Run `npm run bench:checkout` and the p99 crosses 400ms after the third iteration" is a starting point Claude can act on immediately, without first spending a turn asking you how to reproduce it. **Whether it's consistent or intermittent.** This single fact changes the entire strategy. A consistent failure needs one rerun to confirm a fix. An intermittent one, a flaky test, a race condition, a cache invalidation bug that only shows up under load, needs Claude to establish a reproduction rate first: run the failing case in a loop 20 or 50 times, count how often it actually fails, and use that baseline to judge whether a proposed fix genuinely closed the gap or just got lucky on one run. **What "fixed" looks like.** Sometimes this is obvious (the test passes). Sometimes it isn't (the dashboard "looks better," the export "runs faster"). When the definition of done is subjective, make it objective before you start: a specific metric, a before and after screenshot diff, a benchmark number you're targeting. **Address root causes, not symptoms, and say so explicitly.** Claude Code's own documentation flags this as a specific failure pattern worth naming in the prompt itself: if the build fails with an ugly error, it's genuinely easier for a model (and a person) to suppress the error, add a try/catch that swallows it, or comment out the failing assertion, than to trace it back to why it's actually failing. Add "address the root cause, don't suppress the error" to prompts where that risk is real, particularly around tests that guard security-sensitive behavior. It costs you four words and closes off the laziest wrong answer before Claude reaches for it. ## Should you use plan mode when you're debugging? Plan mode makes Claude research and propose a fix before it touches any files, and it earns its overhead specifically when you're uncertain about the approach, the bug spans multiple files, or you don't know the code well enough to sanity-check a diff on sight. For a one-line fix, a missing null check, a typo'd config key, a variable that's off by one, skip it entirely and let Claude fix it directly. The recommended flow has four phases, and it maps onto debugging almost exactly as written: 1. **Explore.** Enter plan mode (press `Shift+Tab`, or start with `claude --permission-mode plan`). Claude reads files and answers questions without making changes. For a bug, this is where you point it at the suspected area: "read src/auth/ and understand how we handle session timeout and token refresh." 2. **Plan.** Ask for a plan once Claude has enough context: "what's actually causing users to get logged out after refresh? Create a plan to fix it." Press `Ctrl+G` to open the plan in your text editor and edit it by hand before Claude proceeds, which matters for a bug fix where you might know a constraint Claude hasn't seen yet, an internal API quirk, a reason the "obvious" fix was avoided before. 3. **Implement.** Exit plan mode and let Claude code against the approved plan, writing and running tests as it goes. 4. **Commit.** Ask Claude to commit with a message that names the root cause, not just the symptom, so the next person reading `git log` (possibly you, in six months) understands what actually broke. Where plan mode pays off hardest for debugging specifically is the second phase. The plan itself becomes a spec you can sanity-check against your own understanding of the system before any file changes exist. If the plan proposes patching the symptom instead of the cause, you catch that on paper, not three edits into a diff you now have to unwind. Where it's overkill: **if you could describe the diff in one sentence, plan mode is friction, not safety.** A prompt like "the config parser crashes on an empty array, add a length check before line 42" doesn't need four phases. Just ask for it. ## Which permission mode should you debug in? Plan mode controls whether Claude edits at all. A separate setting, the permission mode, controls how often Claude has to stop and ask before it acts, and picking the wrong one for a given debugging moment either slows you down with prompts you don't need or removes a safety check you actually wanted. Claude Code's own documentation lays out six modes, and each one trades oversight for speed differently. **Manual mode** (the config value is `default`) approves reads only. Every file edit and every command needs your sign-off. This is the right mode for the first pass at a bug you don't understand yet, or for touching code with real consequences: a payments handler, an auth check, anything where you want to see the exact diff before it lands. **Accept edits mode** (`acceptEdits`) auto-approves file edits and a set of common filesystem commands (`mkdir`, `touch`, `mv`, `cp`, `sed`, and a few others) inside your working directory, while everything else still prompts. This is the mode for a debugging session you're actively reviewing as it happens, since you'll catch the change in `git diff` right after, not three tool calls later. **Plan mode** (`plan`), covered above, blocks edits outright until you approve a plan. It's the mode for exploring a codebase you don't trust yourself to sanity-check on sight. **Auto mode** (`auto`) removes almost every prompt and instead runs a separate classifier model in the background that reviews each action before it executes, blocking anything that escalates beyond what you asked for, targets infrastructure it doesn't recognize, or looks driven by hostile content Claude read somewhere. It blocks things like `curl | bash`, force pushes, production deploys, `git reset --hard`, deleting a stateful resource Claude didn't create, and commenting out a test that guards security behavior, by default, without you having to list any of that yourself. This is the mode for a long, unattended debugging run: a flaky-test reproduction loop, an overnight investigation, anything where you'd rather review a summary at the end than approve forty individual steps. Auto mode needs a specific model tier (Claude Opus 4.6 or later, or Sonnet 4.6 or later, on the Anthropic API) and isn't available on every plan or provider, so check `/model` if it doesn't show up in your `Shift+Tab` cycle. **Don't-ask mode** (`dontAsk`) is the opposite of auto mode's judgment call: it auto-denies anything that isn't already pre-approved by your `permissions.allow` rules or a `PreToolUse` hook, with zero classifier and zero fallback to a human. This is built for CI, not for an interactive debugging session, since a genuinely useful but unlisted action just gets refused instead of asked about. **Bypass permissions mode** (`bypassPermissions`) skips every check, including the protected-path guard around `.git`, `.claude`, and similar directories. Anthropic's own warning is blunt about where this belongs: "Only use this mode in isolated environments like containers, VMs, or dev containers without internet access, where Claude Code cannot damage your host system." Don't run it on your laptop while chasing a bug under time pressure. That's exactly the moment a model's judgment about "just this once" is worst, and this mode removes every check that would catch it. | Mode | What runs without asking | Debugging fit | | --- | --- | --- | | Manual (`default`) | Reads only | First pass at an unfamiliar or sensitive bug | | Accept edits (`acceptEdits`) | Reads, edits, safe filesystem commands | Iterating on a fix you're reviewing live | | Plan (`plan`) | Reads, classifier-approved commands during planning | Exploring before you trust a diff | | Auto (`auto`) | Everything, with background classifier checks | Long, unattended reproduction or investigation | | Don't ask (`dontAsk`) | Only pre-approved tools | CI pipelines and scripted debugging | | Bypass permissions (`bypassPermissions`) | Everything, no checks | Isolated containers only, never your main machine | One detail worth knowing before you lean on auto mode for a debugging run: if the classifier blocks the same action three times in a row, or twenty times total in one session, auto mode pauses and Claude Code drops back to prompting you directly. Approving that one prompt resumes auto mode. In non-interactive mode, with `-p`, there's no user to fall back to, so repeated blocks abort the run instead. If you're chasing a bug against infrastructure the classifier doesn't recognize (an internal staging host, a private registry), that repeated-block pattern usually means it's missing context about your setup rather than something being genuinely wrong, and an administrator can add trusted infrastructure so it stops re-litigating the same call. ## How do hooks catch bugs Claude Code would otherwise miss? Hooks run scripts automatically at fixed points in Claude Code's workflow, and they matter for debugging specifically because they're deterministic where a CLAUDE.md instruction is only advisory. If you tell Claude "always run the linter after editing," it does that most of the time. A `PostToolUse` hook that runs the linter after every edit does it every time, with zero exceptions, because it's code executing on a lifecycle event rather than a suggestion a model has to remember to follow. The two events that matter most for debugging: **PreToolUse** fires after Claude decides on a tool call but before it executes, giving you a synchronous checkpoint to block something before it causes a side effect. This is where you'd stop Claude from running a destructive command while chasing a bug under time pressure, when its judgment about "just this once" might be worse than usual. **PostToolUse** fires after a tool call completes, and this is the workhorse for debugging quality. The most common pattern is a linter or type-checker that runs automatically after every file edit, so a syntax error or a type mismatch surfaces immediately instead of thirty seconds later when the test suite finally gets around to running and fails for a reason that has nothing to do with the actual bug you're chasing. You don't have to hand-write these. Ask Claude directly: *"Write a hook that runs eslint after every file edit"* or *"write a Stop hook that blocks ending the turn until npm test passes."* Claude Code writes hooks well, since the format is just JSON in `.claude/settings.json` under the `"hooks"` key, and reviewing a generated hook is a lot faster than writing one from a blank file. When a hook you configured doesn't fire, the fix is almost always one of a small set of known issues, not a mystery. Claude Code's configuration-debugging documentation lists the actual causes plainly: | Symptom | Usual cause | Fix | | --- | --- | --- | | Hook never fires | `matcher` is a JSON array instead of a string | Use one string with `\|` to match multiple tools, like `"Edit\|Write"` | | Hook never fires | `matcher` value is lowercase, like `"bash"` | Matching is case-sensitive; tool names are capitalized: `Bash`, `Edit`, `Write`, `Read` | | Hook never fires | Hook defined in a standalone file | Hooks live under the `"hooks"` key in `settings.json`, not a separate hooks file | | Hook shows in `/hooks` but still doesn't fire | Matcher mismatch you haven't spotted yet | Start Claude with `claude --debug hooks` and trigger the tool call; the debug log records each event, which matchers were checked, and the hook's exit code | Run `/hooks` first to confirm a hook is even registered before you go looking for a subtler cause. If it's not in that list, it isn't being read at all, and no amount of matcher-tuning will fix that. ## When should you delegate a bug hunt to a subagent instead of doing it yourself? Delegate to a subagent whenever the investigation needs to read a lot of files just to answer one question, because a subagent runs in its own separate context window and only its summary comes back to yours. This is arguably the single highest-leverage habit for debugging in a codebase you don't know well, since exploration is exactly the phase that burns context fastest and contributes the least lasting value to your main conversation once the answer is found. The pattern is a plain-language instruction, not a special syntax: *"use subagents to investigate how our authentication system handles token refresh, and whether we have any existing OAuth utilities I should reuse."* The subagent reads the relevant files, forms an answer, and reports back, typically a few hundred tokens, while the reads that produced that answer, sometimes tens of thousands of tokens across a dozen files, never touch your main session at all. Two debugging-specific uses are worth calling out by name: **Investigation before the fix.** "Use a subagent to trace how the checkout flow calculates tax across all three payment providers" keeps your main context clean for the actual fix once the subagent reports back what it found, instead of accumulating every file it read along the way. **Adversarial review after the fix.** Once Claude implements a fix, a fresh subagent reviewing only the diff, not the reasoning that produced it, catches things the implementing session is biased against seeing. Claude Code's documentation is blunt about why this matters: *"The longer Claude works unattended, the more an independent check matters before you count the work as done. A reviewer running in a fresh subagent context sees only the diff and the criteria you give it, not the reasoning that produced the change, so it evaluates the result on its own terms."* **A reviewer prompted to find gaps in your fix will usually find some, even when the fix is sound, because finding gaps is what it was asked to do.** Tell it to flag only findings that affect correctness or the stated bug, not style preferences, or you'll end up "fixing" a working patch into something more fragile chasing a reviewer's optional suggestions. ## How do checkpoints and /rewind change how you're willing to debug? Every prompt you send creates a checkpoint automatically, and that changes the actual risk calculus of trying something you're not sure about. Instead of carefully reasoning through every possible side effect of a risky diagnostic step before you let Claude take it, you can tell it to try the risky thing, see what happens, and rewind if it doesn't pan out. Press `Esc` twice, or run `/rewind`, to open the rewind menu. From there you can restore code and conversation together, restore just the conversation while keeping current code, restore just the code while keeping the conversation, or summarize from a chosen point to compress a verbose debugging session's early exploration while keeping the working end-state intact. Claude Code keeps snapshots for the 100 most recent checkpoints in a session and deletes them along with the session after 30 days by default, so this isn't a permanent record, but it easily covers a single debugging session, including one that spans several sittings. This matters most in the specific moment where debugging tends to go sideways. **You've tried one fix, it half-worked, you tried a second fix on top of the first, and now you genuinely can't remember which parts of the current diff were doing something useful.** Instead of manually reverting file by file, rewind to the checkpoint right after the first attempt, and either accept that version or try a cleaner second approach from the same clean starting point. One limitation worth knowing before you rely on it under pressure: **checkpointing only tracks changes made through Claude's file editing tools, not files a Bash command modifies.** If Claude ran `rm file.txt` or `mv old.txt new.txt` as part of a diagnostic step, that change isn't in the checkpoint and rewind can't undo it. Checkpoints are local, session-level undo, not a replacement for git. If a debugging session is about to run destructive shell commands, commit your current state to git first, the same way you'd want a save point before letting Claude Code try something you can't fully predict the blast radius of. A rewind attempt can also fail partway: Claude Code will report "Restored the code, but skipped N files" when a file it's trying to restore has since been deleted or made inaccessible outside the session, in which case you restore those specific files by hand. ## Which command do you actually reach for? A decision table Claude Code gives you a genuinely large toolkit for debugging, and picking the wrong one for the moment is the most common source of wasted time. Here's the honest mapping from situation to command, condensed from Claude Code's own best-practices and troubleshooting documentation: | Your situation | What to run | Why | | --- | --- | --- | | You have a bug but no reproduction yet | Describe the symptom, ask for a failing test first | Turns "looks done" into "test passes," which closes the verification loop automatically | | Bug spans multiple files, or you don't know the code | Plan mode (`Shift+Tab`, or `claude --permission-mode plan`) | Review the fix as a spec before any file changes exist | | Need to search a large or unfamiliar codebase for the cause | A subagent (`"use a subagent to investigate..."`) | Reads happen in a separate context window and never touch your main session | | Claude corrected you twice on the same fix | `/clear`, then a more specific prompt | Context is polluted with failed approaches; a clean session with a sharper prompt usually beats fighting the same window | | You tried something risky and it didn't pan out | `/rewind` (or double `Esc`) | Restore code, conversation, or both, to any of the last 100 checkpoints | | You want a fix confirmed by something other than Claude's own judgment | A verification subagent, or a Stop hook running your test suite | A fresh context, or a deterministic gate, catches the trust-then-verify gap | | A file or log is too large and compaction keeps refilling ("thrashing") | Chunk the file, `/compact` with a focus, or a subagent | The single artifact is bigger than one compaction pass can meaningfully shrink | | Claude Code itself seems broken, not your code | `/doctor`, `/debug`, or `claude --safe-mode` | Rules out a broken hook, MCP server, or setting before you keep debugging the wrong layer | | You're chasing a bug that needs many risky steps unattended | Auto mode (`Shift+Tab` to `auto`, where available) | A background classifier reviews each action so you review a summary, not forty prompts | | You're running debugging unattended, in CI or a script | `claude -p "..."` with `--allowedTools` scoped tightly | No terminal session to babysit, so the prompt has to be self-contained and the scope has to be tight | ## A worked example: debugging a flaky test with Claude Code Say your CI keeps flagging `test_concurrent_checkout` as failing, but it passes locally nine times out of ten when you rerun it. This is the debugging scenario most people handle worst, because the instinct is to treat it like a normal bug: reproduce, fix, verify once, move on. That approach fails specifically because a single passing rerun proves almost nothing about an intermittent failure. Here's how the session should actually go: 1. **Tell Claude it's intermittent, not consistent, up front.** "test_concurrent_checkout fails intermittently in CI, maybe 1 in 10 runs, but passes locally most of the time. Suspect a race condition around the inventory decrement." This single detail changes Claude's whole strategy: it now knows a single passing rerun after a fix proves nothing. 2. **Ask for a reproduction rate before any fix.** "Run this test in a loop 30 times and report how many times it fails, before touching any code." This gives you a real baseline, say 4 failures in 30 runs, roughly 13%, instead of guessing at severity from CI's vague "flaky" label. 3. **Let Claude read the actual failure, not just the test name.** A race condition usually shows up as an assertion on a value that's technically correct but arrived in the wrong order, two decrements landing out of sequence, a read happening between a check and a write. Point Claude at the specific assertion that fails and ask it to trace what else touches that shared state concurrently. 4. **Have Claude propose the fix and justify why it addresses a race, specifically.** A lock, a transaction boundary, an atomic operation, whatever the actual mechanism is. If the proposed fix is "add a retry," push back. A retry hides a race condition instead of closing it, and it'll resurface under different timing, usually in production, usually at the worst moment. 5. **Verify against the same reproduction rate, not a single rerun.** "Run the test in a loop 30 times again with the fix applied and report the failure count." Zero failures in 30 runs after 4-in-30 before is real evidence. One clean run after one previous failure is not. **A flaky test that passes once after your fix hasn't been fixed. It's been rerun.** The loop-and-count step is the entire difference between those two outcomes, and it costs you one extra sentence in the prompt. ## A worked example: debugging a production stack trace You get paged with a stack trace and no other context beyond a timestamp and an error message. This is a different shape of problem than the flaky test: you have exactly one occurrence, and reproducing it locally may be difficult or impossible if it depends on production-only data, load, or configuration. 1. **Pipe the raw error into Claude, not a paraphrase of it.** `cat error.log | claude` or a direct paste of the full stack trace preserves line numbers, the exact exception type, and any nested causes that a summary would flatten away. A paraphrased "it threw a null pointer somewhere in the payment handler" throws away information Claude needs and you already have. 2. **Ask Claude to trace the call path the trace implies, not guess at the fix immediately.** "Walk the stack trace against the current code. Is the line number still accurate, or has this file changed since the trace was captured?" Production stack traces from a slightly older deploy are a common trap. The line the trace points to may no longer be the actual problem line. 3. **Check whether it's reproducible at all before assuming you need to.** Some production bugs need specific data shapes, specific load, or a specific timing window that's genuinely hard to recreate locally. If Claude can't build a reliable local repro, the fix still needs verification, just a different kind: a defensive check with logging, deployed and watched, rather than a red-to-green test. 4. **If the error is an API failure from Claude Code's own side mid-session** (a `529 Overloaded`, a `429` rate limit, or a request that times out after the default 10 minutes), that's not your bug. Claude Code's error reference documents these plainly: a `529` means "the API is at capacity, this is usually temporary," and the fix is to check the status page and retry, or switch models with `/model` since capacity is tracked per model, not to start debugging your own code for a problem that isn't there. 5. **Once the fix lands, ask for the evidence, not the assertion.** The exact test that now passes, the exact log line that now doesn't appear, or a description of what you'd need to watch in production to confirm this specific failure mode is closed. ## What do you do when Claude Code itself is broken, not your code? Sometimes the bug isn't in your project. It's in a hook that's silently not firing, an MCP server that connected but returns zero tools, or a CLAUDE.md instruction Claude is inexplicably ignoring. Debugging Claude Code's own configuration uses a completely different toolkit than debugging your code, and reaching for the wrong one wastes real time. If the misbehaving piece is an MCP server specifically, our [complete MCP server setup tutorial](https://tryuncle.com/learn/claude-code/claude-code-mcp-server-setup-tutorial) covers the exact `claude mcp list` and `claude mcp get` commands for diagnosing a "Failed to connect" status before you assume the problem is in your prompt. **Start with `/context`.** It shows everything currently occupying the context window, broken down by category: system prompt, MCP tools, custom subagents and which source each loaded from, memory files, skills, and conversation messages. If your CLAUDE.md isn't showing up in that breakdown, it never loaded, and no amount of rephrasing your instructions will fix a file Claude never read. If it did load but Claude still isn't following a rule inside it, the file is probably too long or the rule too vague; our [guide to writing a CLAUDE.md file](https://tryuncle.com/learn/claude-code/how-to-write-a-good-claude-md-file) covers Anthropic's own recommendation to stay under 200 lines and to phrase rules as imperatives ("use 2-space indentation") instead of preferences ("format code properly"). **Run `/doctor` for a full setup checkup.** It reports invalid settings files, duplicate subagent names, unused extensions, and checked-in CLAUDE.md content Claude can already derive from the codebase, then proposes fixes it applies only after you confirm each one. From your shell rather than inside a session, `claude doctor` prints the same diagnostics without starting a conversation. **Run `/debug [issue]` for a live problem.** It enables debug logging for the current session and prompts Claude to diagnose using the log output and settings paths directly, which is the fastest path when something is misbehaving right now and you don't want to restart to investigate. **Restart with `claude --safe-mode` to isolate the cause.** Safe mode launches a session with every customization disabled, CLAUDE.md, skills, plugins, hooks, and MCP servers, while authentication, model selection, and built-in tools keep working normally. If the problem disappears in safe mode, one of those disabled surfaces caused it, and you can reintroduce them one at a time to find which. If the problem persists even in safe mode, the cause is somewhere outside your usual configuration entirely, which is its own useful piece of information. **Watch for the specific "thrashing" failure if you're debugging a genuinely huge file or log.** You'll see an error starting `Autocompact is thrashing: the context refilled to the limit...`. This means automatic compaction succeeded, but a single artifact, a massive log file, an enormous grep result, refilled the window again immediately afterward, several times in a row. Claude Code stops retrying on purpose rather than burning API calls on a compaction that can't make progress. The documented fix, in order of how surgical it is: chunk the file into smaller reads, run `/compact` with an explicit focus that drops the large output, move the large-file work to a subagent, or `/clear` if the earlier conversation isn't worth keeping. If this keeps happening across sessions rather than on one oversized file, it's worth reading our [guide to Claude Code running out of context mid-session](https://tryuncle.com/learn/claude-code/claude-code-running-out-of-context-mid-session), which walks through the difference between a raised token ceiling and the accuracy drop that a bloated window causes regardless of its size. **If it's a resource problem, not a logic problem, check memory before you check code.** This isn't hypothetical. A real GitHub issue filed against Claude Code documented a session where `VmData`, the process's heap allocation, hit roughly 93 GB after `/resume` ran for over ten minutes at 100% CPU on a Linux machine, dragging system swap usage to 21 GB on a 32 GB RAM box and making the whole machine unresponsive. If a session is crawling and your fans are screaming, run `/heapdump` before you spend another hour assuming the slowness is your test suite's fault. It writes a JavaScript heap snapshot and a memory breakdown to your Desktop (or home directory on Linux without one), showing resident set size, JS heap, array buffers, and unaccounted native memory, which tells you whether the growth is in JavaScript objects or native code before you report it or work around it. ## What do Claude Code's own error messages actually mean? Not every message Claude Code shows you is a bug in your code, and not every one of them is a bug in Claude Code either. Anthropic's error reference groups them into a handful of families, and knowing which family you're looking at tells you immediately whether the fix is on your side, on the API's side, or in your own settings. **Server errors** mean the API itself hit a problem, not your prompt. A `500 Internal server error` is an unexpected failure worth checking status.claude.com for before retrying. A repeated `529 Overloaded` means capacity is temporarily maxed out, and since capacity is tracked per model, switching models with `/model` often gets you working again faster than waiting it out. A request that times out before the connection deadline is usually fixed by breaking a long task into smaller prompts, or raising `API_TIMEOUT_MS` if a slow proxy is the real cause. **Usage limit errors** are about your plan, not a malfunction. Hitting a session, weekly, or Opus-specific limit shows a reset time you can wait out, or you can switch to a different model with `/model`, or buy additional usage with `/usage-credits`. None of these mean anything is broken. **Authentication errors** are almost always a stale credential. "Not logged in" means `/login` will fix it. "Invalid API key" usually means an old `ANTHROPIC_API_KEY` is still set in your shell profile and overriding a newer subscription login, in which case unsetting it and running `/login` again clears it. If your organization disabled a login method entirely, the fix is on the admin side, and the message names the setting they'd need to flip. **Network and connection errors** point at what's between you and the API, not at Claude Code itself. "Unable to connect" is worth confirming with `curl -I https://api.anthropic.com` before you assume a code problem. "SSL certificate verification failed" almost always means a corporate proxy or security appliance is intercepting TLS traffic with its own certificate, fixed by pointing `NODE_EXTRA_CA_CERTS` at your organization's CA bundle rather than by touching your project at all. **Request and context errors** are the ones you'll see most often mid-debugging-session. "Prompt is too long" or "Context exceeds the token limit" both mean the conversation plus attached files exceed the model's window, and `/compact` or `/clear` is the fix, with `/context` telling you what's actually eating the space. A pasted image that's "too large" needs resizing under 8000 pixels on its longest edge, not a smaller prompt. **Tool errors** are configuration problems, not code problems. "File is covered by a Read deny rule" means a permission rule in your settings is blocking a read you actually wanted, not that the file doesn't exist. "Would be spawned with zero tools, refusing" means a subagent's configuration disabled every MCP server it would need, and the fix is to widen that subagent's tool access, not to debug the subagent's prompt. | If you see | It usually means | Not | Fix | | --- | --- | --- | --- | | `529 Overloaded` | API capacity is temporarily maxed | A bug in the fix Claude just wrote | Check status.claude.com, retry, or switch models with `/model` | | `Prompt is too long` | Session accumulated more than the context window holds | Your code is too complex to reason about | `/compact`, `/clear`, or check `/context` for the biggest consumer | | `Invalid API key` | A stale key is shadowing a newer login | Your account has a real access problem | Unset `ANTHROPIC_API_KEY`, run `/login` | | `Autocompact is thrashing` | One artifact keeps refilling the window after each compaction | Claude Code hanging or crashing | Chunk the file, `/compact` with a focus, or hand it to a subagent | | `Auto mode cannot determine the safety of an action` | The classifier check itself failed, usually transiently | Your action being unsafe | Retry after a few seconds; on Bedrock, confirm model access | | `Would be spawned with zero tools, refusing` | A subagent's config disabled every tool it needs | A broken subagent prompt | Widen the subagent's MCP or tool access in its configuration | The pattern across all six families is the same: read which family the message belongs to before you start changing your code. A server error means wait or switch models. A usage error means check your plan. An auth error means re-login. A network error means check the connection, not the request. A request error means manage your context. A tool error means check your settings. Treating any of these as "my fix is wrong" wastes a debugging cycle on a layer that was never broken. ## What about debugging in headless mode or CI? Headless debugging changes the strategy from "manage the session as you go" to "make each invocation small enough that it never needs managing," because `claude -p "prompt"` runs non-interactively with no terminal session to run `/compact` or `/rewind` in partway through. This matters for two distinct debugging use cases: **One-off scripted diagnosis.** `claude -p "Analyze this log file" --output-format stream-json --verbose` pipes Claude into an existing pipeline, and the streaming JSON format gives you one JSON object per line you can parse programmatically rather than a wall of text meant for a human. This is the pattern for a CI step that needs to triage a failure and post a structured result somewhere, not chat about it. **Fan-out debugging across many similar failures.** If a refactor broke the same pattern in 200 files and you have a list of them, loop through with `claude -p` once per file, scoping permissions tightly: ```bash for file in $(cat broken-files.txt); do claude -p "Fix the null-check regression in $file per PATTERN.md. Return OK or FAIL." \ --allowedTools "Edit,Bash(npm test *)" done ``` Test the prompt against 2-3 files first, refine based on what goes wrong, then run the full list. `--allowedTools` matters more here than in an interactive session, since there's no one watching to catch an out-of-scope action before it happens. Use `--verbose` while developing the loop and drop it once the pattern is reliable. One detail worth knowing before you build a debugging step on this: in non-interactive mode, `auto` permission mode aborts the whole run if the safety classifier repeatedly blocks an action, since there's no user to fall back to and prompt, and `dontAsk` mode simply denies anything outside your pre-approved rules instead of pausing. Scope `--allowedTools` deliberately rather than leaning on either mode to sort it out for you in a script nobody's watching run. ## How does debugging in Claude Code actually compare to Cursor, GitHub Copilot, and ChatGPT? They're not solving the same problem, and treating them as interchangeable is where a lot of the "which one is better" debate goes wrong. Here's the honest comparison, stated plainly rather than hedged: | Tool | Where it runs | Debugging strength | Where it falls short for debugging | | --- | --- | --- | --- | | **Claude Code** | Terminal, VS Code, JetBrains, or the web, agentic | Runs your actual test suite, reproduces bugs by executing commands, iterates autonomously against a verification check, delegates large investigations to subagents | Terminal-first workflow has a learning curve if you've only ever debugged inside an IDE's built-in debugger | | **Cursor** | IDE (a VS Code fork) | Deep inline completion and chat right where you're reading code, fast for small, localized fixes | Less built for autonomous multi-step investigation across many files without you steering each step | | **GitHub Copilot** | IDE extension or CLI | Strong for line-level suggestions and chat-based Q&A about a specific file you have open | Not built around an autonomous reproduce-fix-verify loop as its core interaction model | | **ChatGPT (web)** | Browser, no file system access by default | Good for reasoning through a bug you paste in, explaining an unfamiliar error, brainstorming causes | Can't run your test suite, can't read your actual codebase, can't verify its own fix against your real files | **An agent that can run your test suite and read the actual failure beats one that can only reason about a pasted snippet, because verification is the part debugging actually depends on.** That's the core distinction. ChatGPT is genuinely useful for understanding an error message you don't recognize or thinking through possible causes out loud. It cannot close the loop that this entire guide is about, because it has nothing to run and nothing to check against. Cursor and Copilot sit closer to Claude Code in capability, since both can execute commands in increasingly agentic modes now. The practical difference that shows up in debugging specifically is how much of the reproduce-instrument-inspect-fix cycle happens without you driving each individual step. Claude Code was built with that autonomous loop, plan mode, subagents, hooks, checkpoints, as the primary interaction model from the start, rather than as a feature bolted onto an editor's autocomplete engine. If your debugging style is "let it work while I do something else, then review the evidence," that design choice matters more than any single benchmark. Where Claude Code's IDE integrations change the picture slightly: the VS Code and JetBrains extensions run the same agent in the same terminal-driven way, just with the mode indicator and diff review surfaced in the editor UI instead of the raw CLI. If you're used to Cursor's inline diff review, the VS Code extension's mode selector at the bottom of the prompt box is the closest equivalent, and switching between Manual and Accept Edits from there works exactly like `Shift+Tab` does in a bare terminal. ## What does the person who built Claude Code actually say about this? Boris Cherny, who created Claude Code, posted about a moment where his own instinct to debug manually cost him time his coworker didn't lose. Replying to Andrej Karpathy's post about feeling behind on AI tools, he wrote: *"Recently we were debugging a memory leak in Claude Code, and I started approaching it the old fashioned way: connecting a profiler, using the app, pausing the profiler, manually looking through heap allocations."* His coworker, working the same issue, "just asked Claude to make a heap dump, then read the dump to look for retained objects that probably shouldn't be there. Claude 1-shotted it and put up a PR." That anecdote is worth sitting with, because it's not a claim that Claude Code is infallible. It's a much narrower, more useful point: the person who built the tool still occasionally defaults to the debugging habits he had before it existed, and pays for it. **The habit that costs experienced engineers the most time isn't a skill gap. It's forgetting to ask the tool before reaching for the old workflow.** That's a good filter to apply to your own sessions. Before you open a profiler, attach a debugger, or start adding print statements by hand, ask whether describing the symptom and the reproduction step to Claude Code first would get you the same answer faster. Sometimes the old-fashioned way genuinely is faster, particularly for a bug you already half-understand. But it's worth being a deliberate choice rather than a reflex. ## Does this apply if you're debugging Resolve automation, not "real" code? Yes, and the mechanics are identical, only the trigger files are footage-adjacent instead of source files. If you're the kind of editor who also scripts a Fusion macro, automates a render queue, or wires a batch export against DaVinci Resolve's scripting API, you'll hit the exact same bugs everyone else does: a script that half-works, a race condition in a queue that processes files out of order, an API call that silently returns nothing instead of erroring. It's one of the recurring intersections we see in our 100,000+ member professional video-editing community: people who came to editing first and picked up scripting along the way, debugging Python against Resolve's API with the same tools a full-time backend engineer would use. The debugging discipline transfers completely. Give Claude Code a reproduction step ("run this script against test-project.drp and it crashes on the third clip"), let it read the actual traceback, and ask for a verified fix rather than a guess. There's a real, community-maintained bridge worth knowing here if scripting against Resolve is part of your workflow: [samuelgursky/davinci-resolve-mcp](https://github.com/samuelgursky/davinci-resolve-mcp) connects Claude to Resolve's official scripting API through the Model Context Protocol, so Claude Code can run Python commands against your actual open project instead of reasoning about your automation in the abstract. It requires DaVinci Resolve Studio, since the free edition doesn't expose external scripting, and we cover the wider landscape of AI tools people actually reach for with Resolve, including this bridge, in our [roundup of AI tools for learning DaVinci Resolve](https://tryuncle.com/learn/davinci-resolve/ai-tools-to-learn-davinci-resolve). What Claude Code and a scripting bridge don't do is watch your actual Resolve interface while you work inside it. That's a different problem than debugging a script. TryUncle is the on-screen assistant for DaVinci Resolve on macOS. Ask in plain words and Uncle points at the exact control on your screen. If your Fusion macro crashes and you're not sure whether the problem is the script's logic or a setting you missed somewhere in Resolve's own UI, that's the moment Uncle is built for: it watches your project live on the Edit, Color, and Fusion pages and points at the exact control in question, instead of you tabbing between a terminal transcript and a tutorial screenshot from a different version of the app. It's a paid macOS app, currently at founder pricing, $29.99 a month for the first 100 seats, cancel anytime, so check [TryUncle](https://tryuncle.com/?utm_source=tryuncle-learn&utm_medium=blog&utm_campaign=how-to-debug-with-claude-code) for the current rate rather than trusting a number that might change by the time you read this. The next time a Resolve-side script throws something confusing, that's the moment to check: is this a debugging problem Claude Code can trace through the actual error, or a "where is this setting" problem Uncle can point at live inside your project? ## The verdict Debugging with Claude Code isn't a different discipline than debugging always was. It's the same hypothesis-instrument-reproduce-inspect-fix loop, run at a speed that makes some old habits, hand-adding print statements, manually rerunning a flaky test once and calling it fixed, genuinely worse choices than they used to be. The tools that actually move the needle are unglamorous: describe the symptom instead of the vibe, give Claude something to verify against instead of trusting "looks done," pick a permission mode that matches how much you trust the direction, scope investigations or hand them to a subagent, and checkpoint before anything you're not sure about. The failure mode to watch for isn't Claude Code missing a bug. It's a fix that looks plausible, compiles, addresses the symptom you described, and still doesn't handle the case that actually mattered, because nothing in the session forced that case to run. Close that gap with a test, a build, a screenshot, anything that returns pass or fail, and the rest of this guide is really just detail on how to route around the moments that gap tends to show up. Next time you're staring at a stack trace, don't start with "fix this." Start with the reproduction command, what "fixed" actually means, and let Claude Code show you the evidence instead of the assertion. That's the whole workflow, and it scales from a typo to a memory leak without changing shape. ## FAQ ### How do I get Claude Code to actually fix a bug instead of just describing it? Give it something that produces a pass or fail: a failing test, a build command, or a screenshot to diff against a design. Claude Code stops when the work 'looks done,' and without a check to run, that judgment call is the only signal it has. With a check, it runs the fix, reads the result, and keeps iterating until the check passes instead of until it feels finished. ### Why does Claude Code say a bug is fixed when it isn't? Usually because nothing in the session proved it. Claude Code's own best-practices documentation calls this the trust-then-verify gap: a plausible-looking fix that doesn't handle the edge case you actually cared about. Ask for evidence, the test output, the exact command and its result, or a screenshot, rather than accepting a claim of success at face value. ### What's the difference between /debug and /doctor in Claude Code? /debug [issue] turns on debug logging for the current session and asks Claude to diagnose the problem using that log output and your settings paths. /doctor runs a broader setup checkup: installation health, invalid settings files, duplicate subagent names, and configuration problems, and it proposes fixes you confirm before it applies them. Reach for /debug when something inside your session is misbehaving right now, and /doctor when you suspect your setup is the problem. ### Should I always use plan mode when debugging? No. Plan mode earns its overhead when you're uncertain about the fix, the bug spans multiple files, or you don't know the code well yet. For a one-line fix you could describe in a single sentence, like a missing null check or a typo'd config key, skip it and let Claude fix it directly. ### How do I debug a flaky or intermittent test with Claude Code? Tell Claude Code the test is intermittent rather than consistently failing, since that changes the fix strategy entirely: a race condition or shared state needs a loop that reruns the test dozens of times to reproduce it reliably, not a single rerun. Ask it to run the test in a loop first to establish a reproduction rate before touching any code, so you have a baseline to confirm the fix against. ### What do I do when Claude Code itself is the thing that's broken, not my code? Run /doctor for a setup checkup, /debug to turn on logging for the current session, or restart with claude --safe-mode, which disables CLAUDE.md, skills, plugins, hooks, and MCP servers for that session. If the problem disappears in safe mode, one of those surfaces caused it; if it persists, the cause is outside your configuration entirely. ### Is Claude Code better at debugging than Cursor or GitHub Copilot? They solve different problems more than one beats the other outright. Cursor and Copilot are IDE-embedded and lean on inline completion and chat inside the editor you already have open. Claude Code is a terminal-first agent built to run commands, read logs, and iterate against a test suite autonomously, which matters most for debugging that needs to reproduce, instrument, and rerun rather than just suggest a line. ### Can Claude Code help me debug while I'm working in DaVinci Resolve? Indirectly, if the bug is in a Fusion script, a scripting-API automation, or a render pipeline you're building against Resolve. It won't watch your Resolve UI or point at a control on your screen. TryUncle's on-screen assistant, Uncle, does that part: it watches your actual DaVinci Resolve project on the Edit, Color, and Fusion pages and points at the exact control you're asking about, live. ## Sources - [Best practices for Claude Code - Claude Code Docs](https://code.claude.com/docs/en/best-practices) - [Common workflows - Claude Code Docs](https://code.claude.com/docs/en/common-workflows) - [Debug your configuration - Claude Code Docs](https://code.claude.com/docs/en/debug-your-config) - [Troubleshooting - Claude Code Docs](https://code.claude.com/docs/en/troubleshooting) - [Checkpointing - Claude Code Docs](https://code.claude.com/docs/en/checkpointing) - [Choose a permission mode - Claude Code Docs](https://code.claude.com/docs/en/permission-modes) - [Hooks reference - Claude Code Docs](https://code.claude.com/docs/en/hooks) - [Error reference - Claude Code Docs](https://code.claude.com/docs/en/errors) - [Explore the context window - Claude Code Docs](https://code.claude.com/docs/en/context-window) - [Context windows - Claude Platform Docs](https://platform.claude.com/docs/en/build-with-claude/context-windows) - [Effective context engineering for AI agents, Anthropic's Applied AI team](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents) - [Boris Cherny (Claude Code creator) on X, reply to Andrej Karpathy](https://x.com/bcherny/status/2004626064187031831) - [Memory leak: Claude Code process grows to 93 GB heap allocation - GitHub Issue #22188](https://github.com/anthropics/claude-code/issues/22188) - [GitHub - samuelgursky/davinci-resolve-mcp](https://github.com/samuelgursky/davinci-resolve-mcp) - [TryUncle](https://tryuncle.com)