Debugging with Claude Code
Paste the error, give Claude context, watch it diagnose and fix — then learn how to read tool outputs, when to reframe stuck loops, and how to add regression tests so the bug never returns.
Bugs are inevitable. The speed at which you diagnose and fix them is what separates productive developers from stuck ones. Claude Code turns debugging from a solo struggle into a collaborative investigation — but only if you give it the right information to work with.
This lesson covers the full debugging workflow: how to give Claude an error effectively, how to read its tool calls during diagnosis, what to do when Claude gets stuck, and how to close the loop by adding a regression test.
The Core Insight: Give Claude the Full Picture
The most common beginner debugging mistake is giving Claude too little context. Compare these two requests:
Ineffective:
My app is broken. Fix it.
Effective:
I ran `node server.js` and got this error:
TypeError: Cannot read properties of undefined (reading 'id')
at /Users/me/project/routes/user.js:23:15
at Layer.handle [as handle_request] (/Users/me/project/node_modules/express/lib/router/layer.js:95:5)
I was testing the GET /user/:id endpoint. The user ID I passed was "abc123". What is causing this and how do I fix it?
The second version gives Claude the error text, the stack trace (which identifies the exact file and line), what you were doing when it happened, and what input you used. Claude can go directly to routes/user.js:23 and diagnose precisely.
The Debugging Loop
The debugging workflow with Claude follows a consistent loop.
The key discipline: after Claude applies a fix, you test it. Do not ask Claude to assume the fix worked. Actually run the code. If it still fails, report the new (or same) error and iterate. Claude debugs iteratively, just like a human developer would.
Reading Tool Calls During Debugging
When Claude debugs, its tool calls reveal its investigation strategy. Reading them makes you a better developer.
Read calls — Claude is scanning the relevant file to understand the code around the error.
● Running tool: Read(/Users/me/project/routes/user.js)
Grep calls — Claude is searching for how a function or variable is defined or used elsewhere.
● Running tool: Grep(pattern: "getUserById", path: ".")
Bash calls — Claude might run your code, tests, or a quick snippet to verify its hypothesis.
● Running tool: Bash(node -e "console.log(typeof undefined?.id)")
Edit calls — Claude has identified the fix and is applying it.
● Running tool: Edit(/Users/me/project/routes/user.js)
Replace: const userId = req.params.id
With: const userId = req.params?.id ?? null
if (!userId) return res.status(400).json({ error: 'userId required' })
Each tool call represents one step in Claude's reasoning. By reading them, you follow the diagnosis in real time. Over many sessions, you develop intuition for how to approach debugging yourself.
Common Bug Categories and How to Frame Them
Runtime errors (exceptions): Give the full error message and stack trace. Tell Claude what action triggered it.
Logic errors (wrong output): Describe expected behavior vs. actual behavior with specific examples. "I expected calculateTotal([10, 20, 5]) to return 35 but it returned 15."
UI bugs (visual problems): Describe what you see and what you expect to see. "The nav bar is overlapping the hero section on mobile. On a 375px screen, the nav height is 80px but it is sitting on top of the content instead of pushing it down."
Build errors (won't compile/run): Paste the full build output. Build tools usually tell you exactly what is wrong — Claude can parse that output efficiently.
Performance bugs (too slow): Tell Claude which operation is slow and how slow. "Loading the dashboard takes 8 seconds. The bottleneck appears to be the database query in loadDashboardData() — it fetches all records and filters in memory."
When Claude Gets Stuck
Sometimes Claude will propose a fix that does not work. Sometimes it will propose a second fix that also does not work. After two failed attempts with the same approach, stop the loop.
The reframe strategy:
- Run
/clearto wipe the current context (including Claude's assumptions) - Re-describe the problem from scratch, more precisely
- Include what Claude already tried and why it did not work
Example reframe:
Let me restart this debugging session. I have a bug in routes/user.js — GET /user/:id throws a TypeError when userId is undefined. Claude already tried adding optional chaining on req.params.id, but the error persists. Looking at the stack trace again, line 23 is actually inside a callback function where req might be out of scope. Can you read routes/user.js and look specifically at how the callback is structured on and around line 23?
A fresh context, with more precise framing and the failed path explicitly noted, breaks the loop.
Types of Errors Claude Handles Well
Claude is particularly effective at debugging:
- Type errors — undefined, null, wrong type passed to function
- Async/await errors — missing await, unhandled promise rejections
- Import/module errors — wrong path, missing export, circular dependency
- CSS layout bugs — flexbox and grid misconfigurations
- API integration bugs — wrong endpoint, missing headers, payload format issues
- Environment variable bugs — missing vars, wrong var name, not loaded
Claude is less effective at debugging:
- Race conditions — non-deterministic, hard to reproduce from static analysis
- Memory leaks — require profiling tools Claude cannot run
- Hardware-specific issues — GPU, native bindings, platform-specific behavior
Know the category of your bug to set realistic expectations.
Adding a Regression Test After the Fix
Once the bug is fixed, close the loop by asking Claude to add a test:
The bug is fixed. Now add a unit test to the test suite that would have caught this bug — a test that calls GET /user/:id with an undefined userId and expects a 400 response with an error message.
Claude writes the test and integrates it into your existing test file. The next time someone (or Claude itself) introduces the same bug, the test catches it immediately. This is called a — a test that prevents regression to a previously-fixed bug.
This habit — test after fix — is one of the behaviors that separates professional-quality work from amateur work. Claude makes it easy to do by handling the test-writing itself.
Debugging Workflow Example: End to End
You are building your first portfolio page and adding a JavaScript form submission handler. You test it and nothing happens when you click Submit. Here is the full debugging workflow:
Step 1 — Observe: You click Submit. Nothing happens. No error in the console... actually, wait. You open browser DevTools (F12), click Console tab, and see:
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
at script.js:5:24
Step 2 — Paste to Claude:
My form submit handler is not working. Browser console shows:
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
at script.js:5:24
I think it is not finding the form element. Can you read script.js and index.html and diagnose the issue?
Step 3 — Claude diagnoses:
Claude reads both files. It finds that script.js is in the <head> of index.html, which means it runs before the form element exists in the DOM. Claude explains this and offers a fix.
Step 4 — Claude fixes:
Claude moves the <script> tag to just before </body>, or wraps the event listener in DOMContentLoaded.
Step 5 — You test: Refresh the browser. Click Submit. Form works.
Step 6 — Regression test:
Add a comment to script.js explaining why the script tag placement matters (or the DOMContentLoaded wrapper) so future-me understands this.
That is a complete debugging cycle. Twelve minutes from broken to fixed and documented.
Lesson 46 Drill
Introduce a bug deliberately in your web page project:
- Open
script.jsand remove one closing parenthesis from a function call - Refresh the browser and observe the console error
- Start a Claude session and give Claude the full error with context
- Watch Claude diagnose and fix it
- After the fix works, ask Claude to explain what caused the error
This builds the muscle of providing complete debugging context and reading Claude's diagnostic approach.
Bottom Line
Effective debugging with Claude Code is a skill in information packaging: give Claude the full error, the stack trace, the relevant file, and what action triggered it. Read the tool calls to follow the diagnostic reasoning. Use /clear and reframe if Claude hits a dead end after two attempts. Close every bug fix with a regression test. The goal is not just fixing this bug — it is building the habit that makes every future debugging session faster.
Lesson 47 brings everything together: your first full project from blank folder to live site.