Debugging with Claude
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
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 |
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:
- Read the referenced file (
UserList.tsx) - Look at line 23
- Identify what's undefined
- Trace back to find why it's undefined
- 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]
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.
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.
When you report a bug, Claude typically follows this process:
- Reads the relevant files — you'll see it access files
- Identifies the likely cause — explains what's wrong
- Proposes a fix — shows the code change
- 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.
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.
Questions & Answers
Key Takeaways
- Use the formula: expected, actual, error, steps, already tried
- Share full errors — stack traces are gold, don't truncate them
- For logic bugs: describe inputs and expected vs actual outputs
- Guide Claude if it looks in the wrong place
- Ask for the "why" — understanding root cause prevents regression
- 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.