Skip to content

Debugging

AI-assisted debugging strategies -- from stack traces and failing tests to logic errors and performance issues.

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’tDo 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 memoryCopy-paste the actual error output
Fix everything in one passFix one bug, verify, then fix the next

The Three Things Claude Needs

  1. What happened — the exact error message, stack trace, or wrong output
  2. What should happen — the expected behavior
  3. 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:
    • abc1234 PR #245 “Add user profile caching”
    • def5678 PR #247 “Optimize search query performance”
    • ghi9012 PR #248 “Update dependencies”

Why this works: Starting with git context immediately narrows the search from “the entire codebase” to 3 specific changesets.

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 DISTINCT to a JOIN-based approach without deduplication
  • Points out the exact lines: the old query used SELECT DISTINCT r.* FROM results r WHERE ... and the new query uses SELECT 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.ts to display the pre-PR version
  • Searches src/search/ and src/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.