Claude Code is an exceptionally effective debugging partner. It can read error messages, trace code paths, search for patterns across the codebase, and check git history — all in seconds. The key is feeding it the right evidence and asking the right questions.
The Debugging Workflow
1. Reproduce — get an error message, failing test, or log output
2. Feed Claude the evidence — error + relevant code
3. Diagnose — ask Claude to find the root cause
4. Fix — have Claude fix it (one bug at a time)
5. Verify — run tests, check for the same pattern elsewhere
How to Describe Bugs Effectively
The quality of Claude’s diagnosis depends on the quality of evidence you provide.
| Don’t | Do Instead |
|---|---|
| ”Fix all the bugs" | "Fix the NullPointerException on line 63, then run the tests" |
| "Something is wrong with my code" | "Orders over $100 should get free shipping but are being charged $9.99" |
| "Debug this” (with no context) | Paste the error message, stack trace, or test failure |
| Describe the bug from memory | Copy-paste the actual error output |
| Fix everything in one pass | Fix one bug, verify, then fix the next |
The Three Things Claude Needs
- What happened — the exact error message, stack trace, or wrong output
- What should happen — the expected behavior
- Where to look — specific files, functions, or line numbers
Debugging by Bug Type
Failing Tests
Scenario: You run your test suite and get failures. Instead of reading through the entire test and source file yourself, feed the failure to Claude.
Here are my test failures:
InventoryManager#sell
sells items and returns the correct total (FAILED)
Expected: 89.97 (plus or minus 0.01)
Got: nil
InventoryManager#transfer_stock
handles missing source product gracefully (FAILED)
NoMethodError: undefined method '[]' for nil
Analyze these failures. Read the source code in @lib/inventory_manager.rb
and the tests in @spec/inventory_manager_spec.rb, then identify the root
cause of each failure.
Why this works:
- The error messages tell Claude exactly what is wrong (nil instead of 89.97, NoMethodError on nil)
- Referencing specific files with
@gives Claude the context it needs - Asking for “root cause” prevents Claude from just patching symptoms
Follow up — fix one at a time:
Fix the sell method bug first. After fixing it, run the tests again
so we can see if it resolves related failures.
Fixing bugs one at a time is more reliable because you verify each fix independently, later bugs might disappear once earlier ones are fixed, and the diff is easier to review.
Stack Traces
Scenario: Your application crashes in production and you have a stack trace from the logs.
My app is crashing with this error in production:
java.lang.NullPointerException
at com.example.service.OrderProcessor.processOrder(OrderProcessor.java:63)
at com.example.api.OrderController.createOrder(OrderController.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke(...)
Read @src/main/java/com/example/service/OrderProcessor.java
and explain what could be null on line 63. Check all the ways
processOrder can be called and identify which input isn't being
validated.
Why this works:
- Stack traces give Claude the exact file, class, and line number
- Asking “what could be null” focuses Claude on the right question
- Asking Claude to trace callers finds where validation is missing
Logic Errors (No Error Message)
Scenario: The code runs without errors but produces wrong results. This is harder because there is no error message to work from.
The discount calculation in our order processor is giving wrong results.
When a customer orders 50+ items, they should get 10% off, but they're
getting about 14.5% off instead.
Read @src/OrderProcessor.java, specifically the volume discount logic
around lines 80-90. Trace through the calculation for quantity=50 and
tell me where the math goes wrong.
Why this works:
- Describes the expected behavior AND actual behavior
- Points Claude to the specific code section
- Gives a concrete test case (quantity=50) for Claude to trace through
What Claude will find: The code applies both discounts sequentially:
if (quantity >= 10) lineTotal *= 0.95; // 5% off
if (quantity >= 50) lineTotal *= 0.90; // another 10% off
// Result: 0.95 x 0.90 = 0.855 -- 14.5% off, not 10%
Fix: use else if so only one discount applies.
Log File Analysis
Scenario: Something went wrong in your server and you have a log file.
Read @logs/server.log. There's a problem where orders are being processed
twice. Find the log entries that show duplicate processing and identify
the pattern -- is it all orders, specific customers, or specific time windows?
Then read the relevant handler code and find the bug.
Why this works:
- Tells Claude what to look for in the logs (duplicates)
- Asks Claude to identify the pattern first, then find the cause
- This mirrors how experienced engineers debug: evidence first, hypothesis second
Performance Issues
For slow queries:
This SQL query takes 45 seconds on our production database with 10M rows:
[paste the query]
Explain why it's slow. What indexes are missing? Rewrite it for
better performance and explain what changed.
For slow endpoints:
This endpoint responds in 200ms locally but 8 seconds in production.
Read @src/api/reports.ts and identify any N+1 queries, unnecessary
loops, or operations that scale poorly with data size.
Intermittent Bugs
This bug only happens sometimes. Read @src/handlers/payment.ts and look
for race conditions, timing dependencies, or state that isn't being reset
between requests.
Complete Walkthrough: Hunting Down a Production Bug
Scenario: Users report that search results are returning duplicates since Monday’s deploy. Customer support escalated it 20 minutes ago. You need to find the root cause and fix it before standup.
Step 1: Set the Context
You type:
Users are seeing duplicate results in search since Monday. Help me investigate.
Start by checking what was deployed Monday -- look at git log for merges to
main from last week.
Claude does:
- Runs
git log --merges --since="last Monday" --oneline main - Identifies 3 PRs merged Monday:
abc1234PR #245 “Add user profile caching”def5678PR #247 “Optimize search query performance”ghi9012PR #248 “Update dependencies”
Why this works: Starting with git context immediately narrows the search from “the entire codebase” to 3 specific changesets.
Step 2: Narrow the Search
You type:
PR #247 "Optimize search query" looks most relevant. Show me what changed in
that PR -- run git diff for that merge commit focusing on files in src/search/
Claude does:
- Runs
git diff def5678^..def5678 -- src/search/ - Highlights the key change: the query was rewritten from
SELECT DISTINCTto aJOIN-based approach without deduplication - Points out the exact lines: the old query used
SELECT DISTINCT r.* FROM results r WHERE ...and the new query usesSELECT r.* FROM results r JOIN tags t ON ...which produces one row per matching tag
Step 3: Verify the Theory Before Fixing
You type:
That looks like it. Before I fix it, check three things:
1. Are there any existing tests for search deduplication?
2. Show me what the old query looked like in git history
3. Are there other search endpoints that might have the same issue?
Claude does:
- Searches test files for “dedup”, “duplicate”, “distinct” — finds no tests covering deduplication
- Runs
git show def5678^:src/search/queryBuilder.tsto display the pre-PR version - Searches
src/search/andsrc/api/for other query builders — finds 2 more endpoints with the same JOIN pattern without DISTINCT - Reports: “No dedup tests exist. The old query used DISTINCT. Two other endpoints were changed in the same PR and have the same issue.”
Why this works: Validating before fixing prevents you from patching one symptom while missing the bigger problem. The “check three things” pattern gives Claude parallel verification tasks.
Step 4: Fix Across All Affected Files
You type:
Add DISTINCT back to the search query in src/search/queryBuilder.ts, and
add it to the other 2 endpoints too. Then write a regression test that
verifies search results don't contain duplicates when items match
multiple criteria.
Claude does:
- Edits
queryBuilder.ts— adds DISTINCT to the main search query - Edits
advancedSearch.ts— adds DISTINCT to the advanced search query - Edits
suggestions.ts— adds DISTINCT to the suggestions query - Creates a regression test that creates an item with 3 tags, searches for a term that matches 2 tags, and asserts the result contains exactly 1 entry
Step 5: Commit with Context
You type:
Commit this with message "fix: restore DISTINCT in search queries to
prevent duplicate results" and reference PR #247 in the body.
Referencing the original PR in the commit body creates a paper trail. Six months from now, someone looking at git blame will understand both the optimization attempt and why DISTINCT was restored.
Total time: ~5 minutes. A manual investigation in a codebase you know well might take 20 minutes. In an unfamiliar codebase, it could take an hour.
Pattern: Search for the Same Bug Elsewhere
After fixing a bug, search for the same pattern across the codebase.
I just fixed a bug where we were using string comparison instead of
numeric comparison for user IDs. Search the codebase for similar
patterns where IDs or numeric values might be compared as strings.
Check:
- Other ID comparisons (order IDs, product IDs, session IDs)
- Any place where a number from a URL parameter is used without parseInt
- String equality checks on values that should be numeric
Why this works: Bugs rarely exist in isolation. The same mistake that caused one bug often exists elsewhere, especially in code written by the same person or during the same time period. Always ask “are there other places with the same issue?” before closing out a bug fix. Claude can scan the entire codebase in seconds.
Pattern: Understand Before Debugging
When debugging in unfamiliar code, ask Claude to explain before you hunt.
I need to fix a bug in the notification service but I've never worked
in this code before. Read the files in src/notifications/ and explain:
1. What triggers a notification
2. How notifications are queued and delivered
3. What error handling exists
4. Where the most likely failure points are
Then I'll tell you the symptoms and we can narrow down the cause.
Understanding the code before debugging is faster than hunting blindly. Claude’s explanation gives you a mental map so that when you describe the symptoms, you can have an informed conversation about where the bug likely lives.
Configuration Tips
Pre-approve test runner commands
Add to .claude/settings.json so Claude can run tests without asking each time:
{
"permissions": {
"allow": [
"Bash(npm test *)",
"Bash(bundle exec rspec *)",
"Bash(mvn test *)",
"Bash(gradle test *)"
]
}
}
Use batch mode for automated bug scans
Run Claude in non-interactive mode to scan for issues:
claude -p "Read src/services/PaymentService.java and check for null pointer risks, unclosed resources, and error handling gaps. Output as a markdown checklist."
Reset context between debugging sessions
Use /clear when switching between unrelated bugs. Accumulated context from a previous investigation can bias Claude toward the wrong root cause in a new one.
Quick Reference: Debugging Prompts
For test failures
Here's my test output: [paste]. Read [files] and identify the root cause
of each failure. Fix them one at a time, running tests after each fix.
For stack traces
My app crashed with this stack trace: [paste]. Read [file] and explain
what's failing on line [N]. What input validation is missing?
For logic errors
[Feature] is producing [wrong result] instead of [expected result].
Read [file] and trace through the logic for [specific input].
Where does the calculation go wrong?
For performance
This [query/endpoint/function] is slow. Read [file] and identify
operations that don't scale. Suggest specific optimizations.
For intermittent bugs
This bug only happens sometimes. Read [file] and look for race conditions,
timing dependencies, or state that isn't being reset between requests.