| name | debug |
| description | Systematic bug isolation: reproduce, hypothesise, narrow, fix, verify — for any language or error type |
Debug Skill
When to activate
- A bug exists but you don't know where or why
- An error message points to a symptom, not a cause
- A test is failing and the reason isn't obvious
- Intermittent / flaky behaviour that's hard to reproduce
- Something worked before and now it doesn't (regression)
When NOT to use
- You already know the cause — just fix it
- Performance issues — use profiling tools first
- Dependency/version conflicts — check changelogs before debugging code
Instructions
The debugging loop
1. REPRODUCE — make the bug happen reliably
2. HYPOTHESISE — form a specific, testable theory
3. NARROW — eliminate what can't be the cause
4. FIX — change one thing at a time
5. VERIFY — confirm the fix and check for regressions
Never skip step 1. If you can't reproduce it, you can't verify the fix.
Step 1 — Reproduce reliably
def test_reproduces_bug():
result = function_under_test(specific_input_that_fails)
assert result == expected
Questions to answer:
- What exact input triggers it?
- Does it happen every time, or only sometimes?
- Does it happen in production only, or also locally?
- When did it start? (Use
git bisect to find the commit)
Step 2 — Hypothesise specifically
Bad hypothesis: "Something's wrong with the database"
Good hypothesis: "The query returns null when the user has no orders, and we're not handling the null case on line 47"
A good hypothesis is:
- Specific (names a file, function, or line)
- Testable (you can prove or disprove it)
- Falsifiable (you know what would disprove it)
Step 3 — Narrow with binary search
def process_payment(order):
print(f"[DEBUG] order: {order}")
total = calculate_total(order)
print(f"[DEBUG] total: {total}")
result = charge_card(order.card, total)
print(f"[DEBUG] charge result: {result}")
return result
Binary search approach: if a function has 100 lines and you don't know where the bug is:
- Add a print at line 50
- If the print shows correct state, the bug is in lines 51–100
- Add a print at line 75
- Repeat until you've isolated the exact line
Step 4 — Fix one thing at a time
The fix should be the smallest change that makes the test pass. If your fix is more than 10 lines, consider whether you're fixing the root cause or just masking the symptom.
Step 5 — Verify and prevent recurrence
def test_reproduces_bug():
result = function_under_test(specific_input_that_fails)
assert result == expected
Common bug categories and diagnostic commands
NullPointerException / AttributeError / TypeError:
print(type(obj), repr(obj))
assert obj is not None, f"Expected User, got {obj!r}"
Off-by-one:
print(f"len={len(items)}, index={index}, range={range(start, end)}")
Race condition / async bug:
import asyncio
await asyncio.sleep(0.1)
"Works locally, fails in CI":
env | sort > local-env.txt
Regression — worked before, broken now:
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
Flaky test (passes sometimes, fails sometimes):
for i in {1..20}; do pytest tests/test_flaky.py -x && echo "pass $i" || echo "FAIL $i" && break; done
Structured debug prompt for Claude
/debug
Error: {paste the full error message and stack trace}
Code: {paste the function or file, or give the path}
Reproduction: {exact steps / input that triggers it}
What I've tried: {what you've already ruled out}
Expected: {what should happen}
Actual: {what actually happens}
Reading stack traces
Traceback (most recent call last):
File "app.py", line 42, in process_order ← outermost caller
total = calculate_total(order)
File "billing.py", line 17, in calculate_total ← intermediate
return sum(item.price for item in order.items)
File "billing.py", line 17, in <genexpr> ← innermost — READ THIS FIRST
AttributeError: 'NoneType' object has no attribute 'price'
Always start from the bottom of a stack trace. The top is where execution began; the bottom is where it crashed.
Example
Error:
KeyError: 'user_id'
File "api/auth.py", line 34, in get_current_user
return User.get(session['user_id'])
Debug session with Claude:
- Reproduce: add
print(session) before line 34 → reveals session = {}
- Hypothesise: session is empty — either login isn't setting it, or it's being cleared
- Narrow: check login handler —
session['user_id'] = user.id is there. Check middleware — session.clear() is called on every request due to a misconfigured CORS handler
- Fix: Remove the erroneous
session.clear() call
- Verify: test passes, added test for session persistence across requests