debugging-workflow
Systematically reproduce, isolate, diagnose, and resolve software bugs using hypothesis-driven debugging. Use when diagnosing failing test suites, tracing unhandled exceptions, isolating regressions w
SKILL.mdskills/debugging-workflow/SKILL.md
GitHubHypothesis-Driven Debugging Workflow
Debugging is not a trial-and-error guessing game. It is a scientific, evidence-based process of observation, hypothesis formation, experimental isolation, root-cause identification, and regression verification. Never modify code blindly hoping that an issue disappears.
Core Rules
- Reproduce the bug reliably first: Do not write fixes for an issue you cannot reproduce. Create a minimal reproduction script or failing unit test.
- Formulate a falsifiable hypothesis: Before modifying any code, explicitly state: "I hypothesize the crash occurs because function X receives
nullwhen input Y is empty." - Change one variable at a time: If a test fails to confirm the hypothesis, immediately revert the change before trying another.
- Isolate regressions with
git bisect: If something worked yesterday but is broken today, use binary search across git commits to locate the exact breaking commit. - Always write a regression test before fixing: The fix is only complete when the new test fails without the fix and passes with it.
- Understand the root cause, not just the symptom: Don't just slap an
if (x != null)bandage over a symptom if the real bug is that an upstream factory generated an invalid object.
1. The 6-Step Systematic Debugging Cycle
1. Reproduce: Build a minimal, reliable reproduction script or unit test.
│
▼
2. Isolate: Narrow the search space (git bisect, stack traces, binary search in code).
│
▼
3. Hypothesize: State a specific, testable explanation for the observed failure.
│
▼
4. Experiment: Add focused assertion, log statement, or debugger breakpoint.
│
├──> [Hypothesis Disproven] ──> Revert test change, form new hypothesis.
│
▼ [Hypothesis Confirmed]
5. Fix Root Cause: Implement minimal, clean fix addressing root mechanism.
│
▼
6. Verify & Prevent: Run regression test; verify full test suite passes.
2. Isolating Regressions with git bisect
When a known regression occurred between two points in Git history:
# 1. Start bisect
git bisect start
# 2. Mark current broken commit
git bisect bad
# 3. Mark last known good commit or tag
git bisect good v1.2.0
# 4. Git automatically checks out midpoint commits.
# Test the build/reproduction command:
pytest tests/test_parser.py
# If test fails:
git bisect bad
# If test passes:
git bisect good
# 5. Git announces the culprit:
# "[sha] commit message is the first bad commit"
# 6. End bisect and return to original branch
git bisect reset
Automating with git bisect run:
git bisect start HEAD v1.2.0
git bisect run pytest tests/test_parser.py
# Runs automatically in seconds until finding the guilty commit!
3. Creating a Minimal Reproduction Case
Strip away unrelated modules, database fixtures, and external network dependencies until you have the smallest possible script that triggers the failure:
# reproduction.py
from my_app.core import parse_query
# Demonstrates crash: unescaped quotes in filter parameter
def test_reproduce_crash():
raw_query = 'filter="name=O\'Connor"'
result = parse_query(raw_query) # Crashes with QuerySyntaxError
assert result is not None
if __name__ == "__main__":
test_reproduce_crash()
4. Common Mistakes and Anti-Patterns
| Anti-Pattern | Risk | Correct Practice |
|---|---|---|
| "Shotgun Debugging" (changing multiple files at once) | Creates new bugs and masks the true root cause | Change one variable, test, revert if inconclusive |
Symptom patching (if x is None: return) |
Propagates invalid state deeper into application | Fix where the invalid data was created |
| Fixing without a regression test | Bug re-emerges in the next release cycle | Write failing test first, then apply fix |
| Discarding reproduction scripts | Future maintainers cannot verify the fix | Commit the reproduction as a permanent unit test |
| Guessing instead of reading stack traces | Wastes hours looking at the wrong subsystem | Read full stack trace from bottom to top |
5. Verification Checklist
- Bug is captured in an automated, failing regression test.
- Root cause mechanism is understood and documented in commit message.
- Applied fix makes the regression test pass.
- Full existing test suite continues to pass (no unintended side effects).
- Temporary debug prints, logs, or breakpoints have been removed.