debug-systematically
Narrow a bug down instead of guessing at it, and explain the cause before proposing any fix.
Save as .claude/skills/debug-systematically/SKILL.md in a repository, or under ~/.claude/skills/debug-systematically/SKILL.md to have it everywhere. The copy button takes the whole file, frontmatter included.
--- name: debug-systematically description: Find the root cause of a bug by narrowing it down instead of guessing. Use when something is broken, a test fails intermittently, behaviour differs between environments, or someone asks why code is not working. --- # Debug systematically The failure mode in debugging is not a lack of ideas. It is changing things before you understand them, which destroys the evidence and sometimes hides the bug instead of fixing it. Do not propose a fix until step 4. ## 1. Reproduce it Nothing else is worth doing until the bug happens on demand. - Get the exact input, the exact command, the exact environment. - Write down the shortest sequence that triggers it. - If it only happens sometimes, record how often. One in two and one in a thousand point at different causes. If you cannot reproduce it, say so and start gathering what would let you. Do not skip ahead and guess. A fix for a bug you never saw cannot be verified. ## 2. Get the real error Read the actual message and the full trace, not a summary of it. - Find the deepest frame in code you control. That is usually where to look, not the top of the trace. - Read what the error says literally. "Cannot read property of undefined" means something was undefined, so the question is which thing and why, not what general class of bug this resembles. - If an error is being swallowed, find the handler hiding it before anything else. ## 3. Narrow it Halve the search space until the cause is cornered. Pick whichever split fits: - **In time.** It worked before. Find the last good version, then bisect the commits between. - **In space.** Remove parts of the input or disable parts of the system until the failure stops. The last thing you removed is implicated. - **Across environments.** It works locally and fails in staging. List every difference, then test them one at a time: version, configuration, permissions, data, concurrency, clock. State what each test ruled out. "Not the database, because it also fails with a stub" is progress and worth recording. ## 4. Explain it before fixing it Write the causal chain in one or two sentences: this input, through this path, produces this state, which fails here. If you cannot write that sentence, you have not found the cause. You have found a change that makes the symptom go away, which is different and often worse. ## 5. Fix the cause, not the symptom - Fix it where every caller passes through, not in the one place that was reported. A guard in the shared function is a smaller diff than a guard in each caller, and it fixes the siblings that nobody reported yet. - Check the other callers of anything you touch before you touch it. - Add a test that fails without the fix. Run it both ways and confirm. ## Traps - **The intermittent bug is usually ordering, state or time.** Shared mutable state, a race, a test that depends on the previous test, a timezone, a cache. - **"It works on my machine" is data, not an insult.** The difference between the machines is the bug. - **Recent changes are the first suspects**, but not the only ones. A latent bug exposed by a deploy is still a bug in the old code. - **If the fix does not match the explanation, you have not finished.** ## Report back 1. How to reproduce it. 2. The root cause, as a causal chain. 3. What you ruled out along the way. 4. The fix, and the test that proves it.
what it needs
Nothing. This skill is instructions only, so it works in any agent that reads SKILL.md with nothing installed and no account. Save the file and it is live in the next session.
the instructions, in full
Debug systematically
The failure mode in debugging is not a lack of ideas. It is changing things before you understand them, which destroys the evidence and sometimes hides the bug instead of fixing it.
Do not propose a fix until step 4.
1. Reproduce it
Nothing else is worth doing until the bug happens on demand.
- Get the exact input, the exact command, the exact environment.
- Write down the shortest sequence that triggers it.
- If it only happens sometimes, record how often. One in two and one in a thousand point at different causes.
If you cannot reproduce it, say so and start gathering what would let you. Do not skip ahead and guess. A fix for a bug you never saw cannot be verified.
2. Get the real error
Read the actual message and the full trace, not a summary of it.
- Find the deepest frame in code you control. That is usually where to look, not the top of the trace.
- Read what the error says literally. "Cannot read property of undefined" means something was undefined, so the question is which thing and why, not what general class of bug this resembles.
- If an error is being swallowed, find the handler hiding it before anything else.
3. Narrow it
Halve the search space until the cause is cornered. Pick whichever split fits:
- In time. It worked before. Find the last good version, then bisect the commits between.
- In space. Remove parts of the input or disable parts of the system until the failure stops. The last thing you removed is implicated.
- Across environments. It works locally and fails in staging. List every difference, then test them one at a time: version, configuration, permissions, data, concurrency, clock.
State what each test ruled out. "Not the database, because it also fails with a stub" is progress and worth recording.
4. Explain it before fixing it
Write the causal chain in one or two sentences: this input, through this path, produces this state, which fails here.
If you cannot write that sentence, you have not found the cause. You have found a change that makes the symptom go away, which is different and often worse.
5. Fix the cause, not the symptom
- Fix it where every caller passes through, not in the one place that was reported. A guard in the shared function is a smaller diff than a guard in each caller, and it fixes the siblings that nobody reported yet.
- Check the other callers of anything you touch before you touch it.
- Add a test that fails without the fix. Run it both ways and confirm.
Traps
- The intermittent bug is usually ordering, state or time. Shared mutable state, a race, a test that depends on the previous test, a timezone, a cache.
- "It works on my machine" is data, not an insult. The difference between the machines is the bug.
- Recent changes are the first suspects, but not the only ones. A latent bug exposed by a deploy is still a bug in the old code.
- If the fix does not match the explanation, you have not finished.
Report back
- How to reproduce it.
- The root cause, as a causal chain.
- What you ruled out along the way.
- The fix, and the test that proves it.