Debugging with Claude

45 min intermediate Lesson 5

Learning Outcomes

  • Describe bugs effectively for AI-assisted debugging
  • Share error messages and stack traces productively
  • Use Claude's systematic debugging process
  • Set up reproduction steps with Claude's help
  • Verify fixes before moving on

Lesson Plan

Segment Duration Topic
Intro 3 min AI debugging is a superpower
Demo 10 min Debugging a real error
Explain 7 min How to describe bugs well
Demo 10 min Stack traces and error interpretation
Demo 8 min Finding subtle logic bugs
Explain 5 min Fix verification workflow
Wrap-up 2 min Key takeaways

Before You Begin

Pre-work:

  • Complete Lesson 4
  • Ideally have a real bug to debug (or we'll create one)

Shopping List:

  • Claude Code installed
  • A project that runs (web app, CLI, script — anything executable)
  • Basic understanding of error messages in your language

1 The Debugging Formula

Effective bug reports for Claude follow a formula:

What I expected: [expected behaviour]
What actually happened: [actual behaviour]
Error message (if any): [full error text]
Steps to reproduce: [how to trigger the bug]
What I've already tried: [any debugging you've done]

Example — Good bug report:

> I have a bug in the checkout flow:
> 
> Expected: clicking "Place Order" submits the order and redirects to /confirmation
> Actual: the button does nothing, no network request appears in dev tools
> No console errors.
> 
> Steps to reproduce:
> 1. Add any item to cart
> 2. Go to /checkout
> 3. Fill in address form
> 4. Click "Place Order"
> 
> I've checked: the onClick handler is bound, the form validation passes.

Example — Bad bug report:

> The checkout is broken. Fix it.

The first one gives Claude everything it needs. The second requires 5 rounds of Q&A before Claude can even begin.

Why each element matters:

Element What it does for Claude
Expected behaviour Defines the "correct" baseline
Actual behaviour Narrows the search space
Error message Points to exact code location
Steps to reproduce Confirms it's reproducible
Already tried Prevents repeated suggestions
NOTE
Key Insight
The time you invest describing the bug is directly subtracted from the time it takes to fix it. A 30-second description saves 10 minutes of back-and-forth.
TIP
Tip
You don't need all five elements every time. An error with a clear stack trace might only need the error pasted. A silent logic bug with no error message needs more context. Match detail to ambiguity.

2 Error Messages and Stack Traces

When you have an error message, share the FULL thing:

> I'm getting this error when I run the app:
> 
> TypeError: Cannot read properties of undefined (reading 'map')
>     at UserList (src/components/UserList.tsx:23:18)
>     at renderWithHooks (node_modules/react-dom/...)
>     at mountIndeterminateComponent (node_modules/react-dom/...)

Claude will:

  1. Read the referenced file (UserList.tsx)
  2. Look at line 23
  3. Identify what's undefined
  4. Trace back to find why it's undefined
  5. Propose a fix

For runtime errors, include:

  • The full error message (don't truncate)
  • The stack trace (shows where it came from)
  • What triggered it (which action/URL/input)

For build errors:

> npm run build is failing with:
> [paste full build output]

For test failures:

> This test is failing:
> [paste test output including the assertion error]
TIP
Tip
Copy the ENTIRE error, not just the first line. The stack trace tells Claude exactly where to look. Without it, Claude has to guess — which means more iterations.
WARNING
Watch Out
If the error message includes file paths, make sure they're relative to your project root. Absolute paths like /Users/yourname/... don't help Claude navigate.

Complete worked example — a real debugging flow:

Here's the full back-and-forth of a debugging session from error to fix:

You:

> I'm getting this error when I click "Add to Cart":
> 
> TypeError: Cannot read properties of undefined (reading 'id')
>     at addToCart (src/hooks/useCart.ts:34:28)
>     at onClick (src/components/ProductCard.tsx:19:5)

Claude reads files:

[Read] src/hooks/useCart.ts
[Read] src/components/ProductCard.tsx

Claude responds:

I can see the issue. In useCart.ts line 34, the addToCart function 
expects a product object with an id field:

  const addToCart = (product: Product) => {
    const existingItem = items.find(item => item.id === product.id);
    // ...
  };

But in ProductCard.tsx line 19, it's called with just the product ID 
string, not the full product object:

  <Button onClick={() => addToCart(product.id)}>

The fix: pass the full product object instead of just the ID.

Claude applies the fix:

Updated src/components/ProductCard.tsx:
  - onClick={() => addToCart(product.id)}
  + onClick={() => addToCart(product)}

You verify:

> Run the tests for useCart to make sure this doesn't break anything.

Claude runs tests:

$ npm test -- --testPathPattern=useCart

PASS src/hooks/useCart.test.ts
  ✓ adds item to empty cart (3ms)
  ✓ increments quantity for existing item (2ms)
  ✓ removes item from cart (1ms)

All tests passing.

This is the ideal flow: error appears, you paste it, Claude traces the cause, applies a targeted fix, and you verify. Total time: under 2 minutes for a bug that might have taken 10 minutes of manual tracing.

NOTE
Key Insight
Notice how Claude diagnosed the bug by reading TWO files — the caller and the callee. Most bugs live at the boundary between components. Claude's ability to read both sides simultaneously is its debugging superpower.

3 Finding Logic Bugs (No Error Message)

The hardest bugs are ones where nothing crashes — it just produces the wrong result.

Approach 1: Describe expected vs actual

> The total in the shopping cart shows the wrong amount.
> I have 2 items: one at $10, one at $25.
> Expected total: $35
> Actual total: $25
> 
> The relevant code is in src/hooks/useCart.ts — the calculateTotal function.

Approach 2: Ask Claude to trace execution

> Walk me through what happens when calculateTotal() is called 
> with these items: [{price: 10, qty: 2}, {price: 25, qty: 1}]
> What does each step return?

Claude will mentally execute the code and often spot the error (e.g., "It's multiplying by quantity for the first item but not the second").

Approach 3: Ask for a hypothesis

> Users report that notifications sometimes appear twice. 
> I think it might be a race condition but I'm not sure.
> Look at the notification system and tell me what could 
> cause duplicate notifications.

Claude will analyse the code and suggest possible causes, ranked by likelihood.

Approach 4: Give Claude the inputs and let it trace

> Here's the function (src/utils/pricing.ts, line 45):
> 
> function applyDiscount(price, discountPercent) {
>   return price - (price * discountPercent);
> }
> 
> When I call applyDiscount(100, 20) I expect 80 but I get -1900.
> What's wrong?

Claude will immediately spot that 20 is being treated as 20x (2000%) not 20%. The fix is either discountPercent / 100 or passing 0.20 instead of 20. This kind of "trace the logic" debugging is where Claude excels — it can mentally execute code faster than you can set up a debugger.

TIP
Tip
For intermittent bugs (happens sometimes, not always), describe the pattern: 'It only happens when the user clicks fast' or 'Only on the first load after login.' These constraints help Claude narrow the cause.
WARNING
Watch Out
Don't ask Claude to debug problems that require observing runtime state (memory leaks, timing-dependent race conditions, environment-specific issues). Use traditional debugging tools for those, then bring what you learn back to Claude.

4 Claude's Debugging Process

When you report a bug, Claude typically follows this process:

  1. Reads the relevant files — you'll see it access files
  2. Identifies the likely cause — explains what's wrong
  3. Proposes a fix — shows the code change
  4. Optionally verifies — runs tests or the app to confirm

You can guide this process:

If Claude looks at the wrong files:

> The issue is in the API layer, not the frontend. 
> Look at src/api/orders.ts

If Claude's diagnosis is wrong:

> I don't think that's the issue — I already checked and that 
> value is correct. The problem is specifically in the discount 
> calculation, not the subtotal.

If you want deeper analysis:

> Before proposing a fix, explain exactly WHY this bug occurs. 
> What's the sequence of events that leads to the wrong state?

Understanding the "why" is important — it helps you verify the fix is correct and prevents similar bugs in the future.

A deeper diagnosis example:

> Before you fix this, explain exactly what sequence of events 
> leads to the undefined value at line 34.

Claude might respond:

Here's the chain of events:

1. ProductCard renders with a product prop from the parent
2. The parent fetches products from the API
3. During loading, the parent passes product={undefined}
4. ProductCard renders before the fetch completes
5. onClick fires, calling addToCart(undefined.id) → TypeError

The root cause isn't the addToCart call — it's that ProductCard 
renders before the data is ready. The fix should be a loading 
guard in the parent, not a null check in addToCart.

This is why asking "why" matters. A surface-level fix (add a null check) would hide the real problem (missing loading state). Claude's explanation helps you choose the right fix.

NOTE
Key Insight
Don't just accept fixes blindly. Ask Claude to explain the root cause. If the explanation makes sense, the fix is probably correct. If the explanation doesn't make sense, push back.

5 Verifying Fixes

After Claude proposes a fix, verify before moving on:

Ask Claude to run it:

> Apply the fix and run the test suite to make sure nothing broke.

Manual verification:

> Apply the fix. I'll test it manually and come back.

Then test:

  • Does the original bug still occur?
  • Do existing tests still pass?
  • Are there other scenarios that might be affected?

Ask for a regression test:

> The fix looks good. Write a test that would have caught this bug 
> so it doesn't regress.

If the fix doesn't work:

> I applied your fix but the bug still occurs. Here's what I see now:
> [describe current behaviour]

Claude will iterate with this new information. Each iteration narrows the cause — even a "wrong" fix gives Claude new data about what the problem is NOT.

The full verification workflow:

> Apply the fix, then:
> 1. Run the test suite
> 2. Try the original reproduction steps
> 3. Check that related features still work

Claude can do all three in sequence:

$ npm test
PASS (42 tests passed, 0 failed)

Reproduction check: The onClick handler now correctly passes 
the full product object. The cart updates as expected.

Related features: "Remove from cart" and "Update quantity" still 
work because they use item.id from the cart state, not from the 
product prop.
TIP
Tip
After a fix, always ask Claude to write a regression test: 'Write a test that would have caught this bug.' This prevents the same issue from recurring and documents the expected behaviour.
WARNING
Watch Out
A fix that solves the reported bug but breaks something else is worse than no fix. Always run your test suite after debugging changes.

Questions & Answers

Q: Should I always let Claude debug, or sometimes do it myself?
Use Claude for bugs where the problem is clear but the location isn't (searching across files). Debug yourself when you need to understand runtime state (using debuggers, breakpoints, console logging).
Q: What if Claude can't find the bug?
Add more context: reproduction steps, what you've already ruled out, relevant environment details. If Claude still can't find it after 3-4 attempts, the bug may require runtime debugging that AI can't do (timing issues, environment-specific state).
Q: Can Claude debug production issues?
Claude can analyse error logs, stack traces, and code. It can't access production systems directly. Copy relevant logs and errors into the conversation for analysis.
Q: How do I handle a bug where Claude's first fix doesn't work?
Tell Claude specifically what happened: "I applied your fix but now I get [new error]" or "The original bug still occurs — here's what I see." Each failed attempt gives Claude more information. Most bugs resolve within 2-3 iterations. If you hit 4+ attempts, step back and ask Claude to reconsider its assumptions about the root cause.
Q: Should I paste entire files or just the relevant section?
Paste the error message and let Claude read the files itself. Claude is better at finding the relevant context than you are at guessing what it needs. If you pre-trim the context, you might accidentally remove the line that contains the actual bug.

Key Takeaways

  1. Use the formula: expected, actual, error, steps, already tried
  2. Share full errors — stack traces are gold, don't truncate them
  3. For logic bugs: describe inputs and expected vs actual outputs
  4. Guide Claude if it looks in the wrong place
  5. Ask for the "why" — understanding root cause prevents regression
  6. Always verify — run tests after every fix

Next Steps: In Lesson 6 — Git Workflow Integration, you'll learn how Claude supercharges your git workflow with intelligent commits, PR descriptions, and conflict resolution.