systematic-debugging
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes.
SKILL.md.claude/skills/systematic-debugging/SKILL.md
GitHubSystematic Debugging
Overview
Random fixes waste time and create new bugs. Quick patches mask the real issue.
Core principle: ALWAYS find the root cause before attempting a fix. Symptom fixes are failure.
The Iron Law
NO FIX WITHOUT ROOT-CAUSE INVESTIGATION FIRST
If you haven't completed Phase 1, you cannot propose a fix.
When to Use
Any technical issue: test failures, production bugs, unexpected behavior, performance problems, compile/build failures, integration issues. Especially under time pressure, when "one quick fix" seems obvious, or after a previous fix didn't work. Don't skip because the issue "seems simple" — simple bugs have root causes too, and systematic is faster than guess-and-check thrashing.
The Four Phases
Complete each phase before the next.
Phase 1 — Root-cause investigation (before ANY fix)
- Read the error carefully. ExUnit assertion diffs, stack traces,
** (Module)errors, line/file refs — they often contain the exact answer. Don't skim past warnings. - Reproduce consistently. Can you trigger it reliably?
mix test path/to_test.exs:LINE. Not reproducible → gather more data, don't guess. - Check recent changes.
git diff, recent commits, new deps, config/env differences. - Gather evidence across boundaries in multi-component flows (LiveView → context →
Ecto → DB; or a GenServer/PubSub pipeline). Add temporary
IO.inspect(label: ...)ordbg()at each boundary, run once, and see WHERE the data first goes wrong — then investigate that component, not the symptom site. - Trace the bad value backward. Where does it originate? What passed it in? Keep tracing up to the source. Fix at the source, not where it surfaced.
Phase 2 — Pattern analysis
- Find similar working code in this codebase. What works that's like the broken thing?
- If following a pattern/reference, read it COMPLETELY — don't skim.
- List every difference between working and broken, however small ("that can't matter" is where the bug hides). Note the config/env/assumptions each relies on.
Phase 3 — Hypothesis and testing
- State ONE hypothesis explicitly: "I think X is the root cause because Y."
- Test it with the SMALLEST possible change — one variable at a time.
- Worked? → Phase 4. Didn't? → form a NEW hypothesis; don't stack fixes on top.
- Don't understand something? Say so and investigate — don't pretend.
Phase 4 — Implementation
- Write a failing test first that reproduces the bug (use the
test-driven-developmentskill). You must have it before fixing — it proves the fix and prevents regression. - One fix, addressing the root cause. No "while I'm here" improvements, no bundled refactoring.
- Verify (use
verification-before-completion): the new test passes, nothing else broke,mix precommitis green. - If the fix doesn't work: STOP. Count your attempts. < 3 → return to Phase 1 with the new information. ≥ 3 failed fixes → stop guessing and question the architecture (next).
When 3+ fixes fail — question the architecture
If each fix reveals new coupling/shared state elsewhere, or every fix needs "massive refactoring," or each fix spawns new symptoms — that's not a failed hypothesis, it's the wrong design. Stop and discuss with the user whether the pattern itself is sound before attempting fix #4.
Red Flags — STOP and return to Phase 1
"Quick fix now, investigate later" · "just try changing X and see" · "add several changes, run tests" · "it's probably X, let me fix that" · "I don't fully get it but this might work" · proposing fixes before tracing data flow · "one more fix attempt" after 2+ failures · each fix uncovering a new problem in a different place.
Common Rationalizations
| Excuse | Reality |
|---|---|
| "Issue is simple, skip the process" | Simple bugs have root causes too; the process is fast for them. |
| "Emergency, no time" | Systematic is faster than guess-and-check thrashing. |
| "Try this first, investigate later" | The first fix sets the pattern. Do it right from the start. |
| "I'll test after confirming the fix" | Untested fixes don't stick. Test-first proves it. |
| "Multiple fixes at once saves time" | You can't isolate what worked; it causes new bugs. |
| "I see the problem, let me fix it" | Seeing the symptom ≠ understanding the root cause. |
Related skills
test-driven-development— for the Phase 4 failing test.verification-before-completion— to confirm the fix before claiming success.