Day 16: Debugging with Claude Code
Learn effective debugging with Claude Code. Three things needed: full error, where it happened, what triggered it. Let Claude trace root causes and fix them.
Hey, it's G.
Day 16 of the Claude Code series.
Debugging used to be my least favorite part of building.
Read the error. Google it. Stack Overflow. Try a fix. Hope it works. Repeat.
Claude Code collapses that entire loop.
But only if you give it the right context.
Let me show you how to debug effectively — without becoming the middleman between the error and the fix.
The Problem (Debugging the Old Way)
Here's how I used to debug before Claude Code:
- Error appears
- Read the error message
- Google the error
- Read Stack Overflow answers
- Try to understand the fix
- Manually apply it to my code
- Hope it works
- If not, repeat
Every. Single. Bug.
With AI but without Claude Code:
- Error appears
- Copy error message
- Paste into ChatGPT
- Get a generic answer
- Manually apply it
- Doesn't work because the answer was too generic
- Add more context
- Get another answer
- Manually apply it
- Still doesn't work
Still the middleman. Still doing all the manual work.
The Concept (Let Claude Code Trace It)
Most developers use Claude Code for debugging the same way they used Stack Overflow:
Paste the error → Get an answer → Apply it manually
That works. But it's the slowest version of this workflow.
The faster version:
Give Claude Code the error, the context, and the permission to trace it through your actual codebase.
Let it:
- Read the files
- Follow the call chain
- Find the root cause
- Fix it
You review the fix. Done.
The mistake that makes debugging sessions go wrong:
Giving Claude Code too little context.
An error message alone is almost useless — it's the symptom, not the cause.
The cause lives somewhere in your files.
Claude Code needs to be able to navigate there.
The Three Things Claude Code Needs to Debug
1. The Full Error
Complete message and stack trace. Not just the first line.
❌ Bad (incomplete):
Error: Cannot read property 'email'
✅ Good (complete):
TypeError: Cannot read properties of null (reading 'email')
at UserDashboard (/app/dashboard/page.tsx:18:28)
at renderWithHooks
at updateFunctionComponent
at beginWork
at HTMLUnknownElement.callCallback
The stack trace shows the path through your code. Claude needs it.
2. Where It Happened
File name, function, line number if you have it.
Not just:
Error happened in the dashboard
But:
Where it happened: UserDashboard component in
/app/dashboard/page.tsx, line 18
Specific location = Claude knows where to start reading.
3. What Triggered It
What action caused the error?
Not just:
The app crashed
But:
What triggered it: Refreshing the dashboard page
while logged in. User object seems to be null on
initial render even when authenticated.
This tells Claude the conditions that expose the bug.
The Basic Debugging Prompt
Here's the template I use:
I have an error. Here are the details:
Error:
[paste full error message and stack trace]
Where it happened:
[file name, function, line number if known]
What triggered it:
[what action or conditions caused this error]
Read the relevant files, find the root cause,
and fix it. Show me what you changed and why.
The /debug Slash Command
Build this command once, use it forever:
touch ~/.claude/commands/debug.md
Inside the file:
I have a bug to fix. I will provide:
- The full error message and stack trace
- Where it happened
- What triggered it
Read all relevant files to trace the root cause.
Do not fix symptoms — find the actual source of the problem.
Explain what caused it before making any changes.
Then fix it and show me exactly what changed and why.
Use it every time:
claude
> /debug
>
> Error: Cannot read properties of undefined (reading 'map')
> at SubscriptionList (components/billing/SubscriptionList.tsx:24)
>
> Where it happened: SubscriptionList component, line 24
>
> What triggered it: Navigating to /billing when user
> has no subscriptions yet
Claude responds:
Reading components/billing/SubscriptionList.tsx...
Root cause identified:
Line 24 calls subscriptions.map() but subscriptions is
undefined when the user has no subscription data yet.
The component doesn't check if subscriptions exists before
trying to map over it.
Fix: Add optional chaining and provide empty array fallback.
Applying fix to components/billing/SubscriptionList.tsx...
Changed:
- Line 24: {subscriptions.map(sub => ...)}
+ Line 24: {subscriptions?.map(sub => ...) ?? []}
This prevents the error when subscriptions is undefined
and renders an empty state instead.
Root cause explained. Fix applied. Done.
Real Debugging Session (Complete Example)
Let me walk you through a real bug I hit in Resiboko.
The Bug
What should happen: Dashboard loads and shows user's expense data
What actually happens:
TypeError: Cannot read properties of null (reading 'email')
Dashboard crashes immediately.
Step 1: Debug
cd ~/projects/resiboko
claude
> /debug
>
> Error: TypeError: Cannot read properties of null
> (reading 'email')
> at UserDashboard (/app/dashboard/page.tsx:18:28)
> at renderWithHooks
> at updateFunctionComponent
>
> Where it happened: UserDashboard component, line 18
>
> What triggered it: Refreshing the dashboard page
> while logged in
>
> The user object seems to be null on initial render
> even when the user is authenticated.
Step 2: Claude Traces the Issue
Claude responds:
Reading /app/dashboard/page.tsx...
Reading /lib/supabase.ts...
Reading /hooks/useUser.ts...
Root cause found:
The useUser hook returns null on first render before
the auth state resolves.
The UserDashboard component tries to access user.email
on line 18 without checking if user exists first.
This causes the TypeError on initial render.
The auth flow:
1. Component mounts, useUser returns null
2. Component tries to render user.email → crash
3. Auth resolves (but it's too late)
Fix needed: Add loading state check before rendering
user-dependent content.
Applying fix to /app/dashboard/page.tsx...
Step 3: The Fix
Changes made to /app/dashboard/page.tsx:
Before (line 15-20):
export default function UserDashboard() {
const user = useUser()
return (
<div>
<h1>Welcome, {user.email}</h1>
</div>
)
}
After (line 15-25):
export default function UserDashboard() {
const user = useUser()
if (!user) {
return <div>Loading...</div>
}
return (
<div>
<h1>Welcome, {user.email}</h1>
</div>
)
}
Added null check before trying to access user properties.
Now shows loading state while auth resolves.
Step 4: Review
> /review
Claude responds:
Review findings:
/app/dashboard/page.tsx:
✅ Null check added correctly
✅ Loading state displays while auth resolves
✅ No TypeScript errors
✅ Follows existing pattern from /app/profile/page.tsx
Summary: Fix looks good. No issues found.
Step 5: Write a Test
> Write a test for the UserDashboard component that
> covers the null user state on initial render
Claude creates test file:
// app/dashboard/__tests__/page.test.tsx
import { render, screen } from '@testing-library/react'
import UserDashboard from '../page'
import { useUser } from '@/hooks/useUser'
jest.mock('@/hooks/useUser')
describe('UserDashboard', () => {
it('shows loading state when user is null', () => {
(useUser as jest.Mock).mockReturnValue(null)
render(<UserDashboard />)
expect(screen.getByText('Loading...')).toBeInTheDocument()
})
it('shows user email when user is loaded', () => {
(useUser as jest.Mock).mockReturnValue({
email: '[email protected]'
})
render(<UserDashboard />)
expect(screen.getByText(/Welcome, [email protected]/))
.toBeInTheDocument()
})
})
Bug found. Root cause explained. Fix applied. Test written.
Three prompts. Done.
Advanced Debugging Patterns
Pattern 1: No Obvious Location
When you don't know where the error is coming from:
> /debug
>
> Error: JWT expired
> [full stack trace]
>
> I'm not sure where this is coming from.
>
> Read through /lib and /app/api to find where
> JWT validation happens and trace the issue from there.
Claude will search through your codebase to find the source.
Pattern 2: No Error Message
When something's broken but there's no error:
> There's no error message but something is wrong.
>
> What should happen: Clicking Save updates the user's
> display name and shows a success message
>
> What actually happens: The button shows a loading
> spinner forever and nothing saves
>
> Start at /components/profile/ProfileForm.tsx and
> trace the save flow to find where it's getting stuck.
Describe the wrong behavior. Claude traces the flow.
Pattern 3: Intermittent Bug
When the bug only happens sometimes:
> /debug
>
> Error: [paste error]
>
> This only happens when:
> - User is on mobile
> - They navigate from /dashboard to /billing
> - Their subscription is Free tier
>
> Start at the /billing page and check for any
> conditional logic that depends on these conditions.
Give Claude the reproduction steps.
Pattern 4: Performance Bug
When it's slow but not broken:
> The /dashboard page is really slow to load.
>
> What should happen: Page loads in under 1 second
>
> What actually happens: Takes 5-8 seconds
>
> Read /app/dashboard/page.tsx and trace all the
> data fetching. Find what's causing the slowdown.
Claude will identify expensive operations.
After Every Bug Fix
Always run this sequence:
1. Review the Fix
> /review
Make sure the fix is correct and follows your patterns.
2. Write a Test
> Write a test that would have caught this bug
Or more specific:
> Write a test for [the function that had the bug]
> that covers [the condition that caused the bug]
This habit changed everything for me.
I've caught the same bug trying to creep back in twice now because there was a test waiting for it.
The Debug Workflow
Here's the complete workflow I use every time:
1. Hit a bug
2. Gather context:
- Full error message and stack trace
- Where it happened (file, function, line)
- What triggered it (action or conditions)
3. Debug:
> /debug
> [paste all context]
4. Claude traces root cause and fixes it
5. Review:
> /review
6. Test:
> Write a test that covers this bug
7. Ship:
> /ship
What Makes This Work
The "Explain Before Fixing" Rule
Notice in the /debug command:
Explain what caused it before making any changes.
This is critical.
Without this, Claude sometimes fixes the symptom and moves on.
With this, you learn:
- What actually caused the bug
- Why the fix works
- How to avoid similar bugs
You're not just fixing bugs faster. You're understanding your codebase better.
Reading Multiple Files
Claude Code can trace through your entire codebase:
Reading /app/dashboard/page.tsx...
Reading /lib/supabase.ts...
Reading /hooks/useUser.ts...
It follows the call chain across files to find the root cause.
You can't do this as fast manually.
Fixing Symptoms vs Root Causes
Bad debugging:
Error: user.email is null
Fix: Add user?.email with optional chaining
This fixes the symptom. The error is gone. But the root cause (why user is null) is still there.
Good debugging:
Error: user.email is null
Root cause: useUser returns null on first render
before auth resolves
Fix: Add loading state check before rendering
user-dependent content
This fixes the root cause. The error can't happen again.
My Raw Notes (Unfiltered)
The context thing is huge. Used to just paste the error and get a generic answer.
Started giving the full stack trace plus what triggered it and the quality of the debug session went up immediately.
The "explain the root cause before fixing" instruction is important — without it Claude sometimes fixes the symptom and moves on.
The write a test after fixing habit is something I started recently and it's caught the same bug coming back twice already.
For bugs with no error message, the "describe the wrong behavior" approach works surprisingly well.
Debugging isn't just about fixing things fast anymore. It's about understanding why they broke.
Tomorrow (Day 17 Preview)
Topic: Refactoring with Claude Code — how to clean up messy code without breaking anything and without doing it line by line yourself.
What I'm testing: Can Claude Code refactor entire files while preserving behavior? How do you guide it to improve code structure without introducing new bugs?
Following This Series
Phase 3 (Days 15-21): Vibe Coding ⬅️ You are here
So far in Phase 3:
- Day 14: Vibe coding mindset
- Day 15: Feature-first workflow
- Day 16: Debugging with Claude Code (today)
- Day 17: Refactoring with Claude Code (tomorrow)
G
P.S. - Three things for every debug: full error + stack trace, where it happened, what triggered it. Context is everything.
P.P.S. - Always end debug sessions with two commands: /review to check the fix, then write a test so the bug can't come back quietly.
P.P.P.S. - The "explain before fixing" rule turns debugging into learning. You're not just fixing bugs faster — you're understanding your codebase better.