| name | systematic-debugging |
| description | Use when encountering any bug, test failure, unexpected behavior, or broken build — before proposing any fix |
Systematic Debugging
Overview
Random fixes waste time and introduce new bugs. Quick patches hide root causes.
Core principle: Find the root cause before touching anything. A fix without a root cause is a guess.
Announce at start: "I'm using the systematic-debugging skill for this issue."
The Iron Law
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
If you haven't completed Phase 1, you cannot propose a fix. Not even a "small" one.
When to Use
Use for ANY technical issue:
- Test failures
- Bugs in production or development
- Unexpected behavior or wrong output
- Build or deploy failures
- Performance problems
- Integration issues
Use this ESPECIALLY when:
- You're under time pressure (emergencies make guessing tempting — don't)
- "Just one quick fix" seems obvious
- You've already tried a fix and it didn't work
- You don't fully understand why it's failing
- The same bug keeps coming back
Don't skip when:
- Issue seems simple (simple bugs have root causes too)
- You're in a hurry (systematic is faster than thrashing — always)
- Someone wants it fixed immediately (guessing takes longer than investigating)
The Four Phases
You MUST complete each phase before proceeding to the next.
Phase 1: Root Cause Investigation
Before attempting ANY fix:
-
Read error messages completely
- Don't skim — read the full stack trace
- Note exact error text, line numbers, file paths, error codes
- Errors often contain the exact fix — read them before doing anything else
-
Reproduce it consistently
- Can you trigger it reliably?
- What are the exact steps?
- Does it always fail, or only sometimes?
- If not reproducible → gather more data first. Never guess on a flaky issue.
-
Check what changed
- What was the last thing that changed before it broke?
git diff, recent commits, new dependencies, config changes
- Environment differences (local vs CI vs prod)?
- New env vars added or removed?
-
Instrument multi-component systems
When the system has multiple layers (API → service → database, CI → build → deploy):
Don't guess which layer — instrument each boundary:
For each component boundary:
- Log what enters the component
- Log what exits the component
- Verify config/env propagation at each layer
Run once → gather evidence → identify which layer breaks
Then investigate that specific layer
Example for a failing deployment pipeline:
echo "API_KEY: ${API_KEY:+SET}${API_KEY:-MISSING}"
env | grep API_KEY || echo "NOT in build environment"
curl /api/health | jq '.env.API_KEY'
This tells you exactly where it breaks — don't fix before running this.
-
Trace the data flow
- Where does the bad value originate?
- What called this function with the bad input?
- Keep tracing backward through the call stack until you find the source
- Fix at the source — never at the symptom
Gate: You must be able to state: "The root cause is X because Y." If you can't, keep investigating.
Phase 2: Pattern Analysis
Find the pattern before forming a hypothesis:
-
Find working examples
- Where does similar code work correctly in the same codebase?
- What's different between the working version and the broken version?
-
Read references completely
- If implementing a pattern (auth, pagination, webhooks), read the reference implementation in full
- Don't skim and adapt — partial understanding guarantees bugs
- Understand the pattern before applying it
-
List every difference
- Working vs broken — list ALL differences, however small
- Never assume "that can't matter" — it often does
- Differences in: types, config keys, initialization order, async vs sync, env context
-
Map dependencies
- What does this component assume about its environment?
- What other components does it depend on?
- What breaks if one of those assumptions is wrong?
Gate: You must be able to state: "The working version does X. The broken version does Y. The difference is Z."
Phase 3: Hypothesis and Testing
Scientific method — one variable at a time:
-
Form a single hypothesis
- State it clearly: "I think X is the root cause because Y"
- Write it down before testing
- Be specific — "something in auth" is not a hypothesis
-
Test minimally
- Make the SMALLEST possible change to test the hypothesis
- One variable at a time — never bundle changes
- If the test is destructive or irreversible, stage it first
-
Verify before continuing
- Did the hypothesis hold? Yes → move to Phase 4
- No → form a NEW hypothesis from the new evidence
- Never stack fixes on top of a failed hypothesis
-
When you genuinely don't know
- Say so: "I don't understand why X is happening"
- Don't pretend — guessing confidently is worse than admitting uncertainty
- Gather more evidence or ask for help
Gate: Hypothesis confirmed with minimal test. One hypothesis per test cycle.
Phase 4: Implementation
Fix the root cause, not the symptom:
-
Write a failing test case first
- Simplest possible reproduction of the bug
- Automated test if the project has a test framework
- One-off script if not
- This test must fail before your fix — that's how you know you're fixing the right thing
-
Implement one fix
- Fix ONLY the root cause identified
- One change at a time
- No "while I'm here" improvements
- No bundled refactoring
-
Verify the fix
- Your test passes now?
- No other tests broken?
- Issue actually gone — not just hidden?
-
If the fix doesn't work
- STOP
- Count how many fixes you've tried
- Under 3 → return to Phase 1 with the new evidence
- 3 or more → STOP. The architecture is wrong. Don't attempt another fix.
-
If 3+ fixes have failed: question the architecture
Signs you're fighting the wrong battle:
- Each fix reveals a new problem in a different place
- Fixing one thing breaks another
- The fix requires "massive refactoring" to even attempt
Stop fixing. Question fundamentals:
- Is this pattern fundamentally sound?
- Are we stuck with a broken approach out of inertia?
- Should we refactor the architecture instead of patching symptoms?
Discuss with the user before attempting anything else.
Gate: Test written → passes after fix → no regressions → can explain the fix in one sentence.
Red Flags — STOP and Return to Phase 1
If you catch yourself thinking any of these:
- "Quick fix for now, investigate later"
- "Just try changing X and see if it works"
- "It's probably X, let me fix that"
- "I'll add a few changes and run tests"
- "Skip the test, I'll verify manually"
- "I don't fully understand but this might work"
- "One more fix attempt" (after 2+ failures)
- Proposing solutions before tracing the data flow
- Listing fixes before completing Phase 1
- Each fix is revealing a new problem in a different place
ALL of these mean: STOP. Return to Phase 1.
3+ fixes failed? Don't fix again — question the architecture.
User Signals You're Doing It Wrong
Watch for these — they mean you're guessing instead of investigating:
| User says | What it means |
|---|
| "Is that actually happening?" | You assumed without verifying |
| "Did you check the logs?" | You proposed a fix without gathering evidence |
| "Stop guessing" | You're cycling through fixes without root cause |
| "Why is it doing that?" | You explained the symptom but not the cause |
| "We're still stuck?" | Your approach isn't working — stop and restart Phase 1 |
When you see these: STOP. Return to Phase 1.
Common Rationalizations
| Excuse | Reality |
|---|
| "Issue is simple, don't need process" | Simple bugs have root causes too. Process is fast for simple bugs. |
| "Emergency, no time for process" | Systematic debugging is FASTER than thrashing. Always. |
| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. |
| "I'll write the test after the fix" | Untested fixes don't stick. Test first — it proves you fixed the right thing. |
| "Multiple changes at once saves time" | Can't isolate what worked. Guarantees new bugs. |
| "I skimmed the reference, I get the pattern" | Partial understanding guarantees bugs. Read it fully. |
| "I can see the problem" | Seeing the symptom ≠ understanding the root cause. |
| "One more fix attempt" (after 2+ failures) | 3 failures = architectural problem. Stop fixing, question design. |
Quick Reference
| Phase | What you do | Gate |
|---|
| 1. Root Cause | Read errors, reproduce, check changes, instrument layers, trace data flow | Can state: "Root cause is X because Y" |
| 2. Pattern | Find working examples, read references, list differences | Can state: "Working does X. Broken does Y. Diff is Z." |
| 3. Hypothesis | Form one hypothesis, test minimally, verify | Hypothesis confirmed with evidence |
| 4. Implementation | Write failing test, single fix, verify, commit | Test passes, no regressions, one-sentence explanation |
When Investigation Reveals "No Root Cause"
If systematic investigation shows the issue is truly environmental, timing-dependent, or external:
- Document what you investigated and what you ruled out
- Implement appropriate handling: retry logic, timeout, clear error message
- Add logging/monitoring so the next occurrence has more data
- Note it in learning-log.md for future reference
But: 95% of "no root cause" cases are incomplete investigation.
Supporting Techniques
Root cause tracing — when error is deep in a call stack:
- Start at the error site
- Ask: what called this? What value did it pass?
- Trace backward until you find where the bad value was introduced
- Fix there — not at the error site
Defense in depth — after finding root cause:
- Fix the source
- Add validation at the boundary where bad data entered
- Add a test that would have caught it earlier
Condition-based waiting — for timing/async issues:
- Replace arbitrary
sleep() calls with polling for a specific condition
while not ready: check_condition() beats sleep(2) every time
Related Skills
- continuous-improve — wraps this skill in the full Research → Plan → Execute → Verify → Reflect loop
- Any test-writing skill in your agent's library — use for Phase 4 test creation