Bug Killer — Hypothesis-Driven Debugging Workflow
Execute a systematic debugging workflow that enforces investigation before fixes. Every bug gets a hypothesis journal, evidence gathering, and root cause confirmation before any code changes.
Phase Overview
- Triage & Reproduction — Understand, reproduce, route to quick or deep track
- Investigation — Gather evidence with language-specific techniques
- Root Cause Analysis — Confirm root cause through hypothesis testing
- Fix & Verify — Fix with proof, regression test, quality check
- Wrap-up & Report — Document trail, capture learnings
Phase 1: Triage & Reproduction
Goal: Understand the bug, reproduce it, and decide the investigation track.
1.1 Parse Context
Extract from the provided inputs and conversation context:
- Bug description: What's failing? Error messages, symptoms
- Reproduction steps: How to trigger the bug (test command, user action, etc.)
- Environment: Language, framework, test runner, relevant config
- Prior attempts: Has the user already tried fixes? What didn't work?
- Deep flag: If
--deepis present, skip triage and go directly to deep track (jump to Phase 2 deep track)
1.2 Reproduce the Bug
Attempt to reproduce before investigating:
- If a failing test was mentioned, run it:
``bash # Run the specific test to confirm the failure <test-runner> <test-file>::<test-name> ``
- If an error was described, find and trigger it
- If neither, search for related test files and run them
Capture the exact error output — this is your primary evidence.
If the bug cannot be reproduced:
- Prompt the user for more context
- Check if it's environment-specific or intermittent
- Note "not yet reproduced" in the hypothesis journal
1.3 Form Initial Hypothesis
Based on the error message and context, form your first hypothesis:
### H1: [Title]
- Hypothesis: [What you think is causing the bug]
- Evidence for: [What supports this — error message, stack trace, etc.]
- Evidence against: [Anything that contradicts it — if none yet, say "None yet"]
- Test plan: [Specific steps to confirm or reject]
- Status: Pending
1.4 Route to Track
Quick-fix signals (ALL must be true):
- Clear, specific error message pointing to exact location
- Localized to 1-2 files (not spread across the codebase)
- Obvious fix visible from reading the error location
- No concurrency, timing, or state management involved
Deep-track signals (ANY one triggers deep track):
- Bug spans 3+ files or modules
- Root cause unclear from the error message alone
- Intermittent or environment-dependent failure
- Involves concurrency, timing, shared state, or async behavior
- User already tried fixes that didn't work
- Generic error message (e.g., "null reference" without clear origin)
- Stack trace points to library/framework code rather than application code
Present your assessment to the user:
- Summarize the bug and your initial hypothesis
- Recommend quick or deep track with justification
Prompt the user:
- "Quick track (Recommended)" / "Deep track" — depending on your assessment
- Let the user override your recommendation
Track escalation rule: If during quick track execution, 2 hypotheses are rejected, automatically escalate to deep track. Preserve all hypothesis journal entries when escalating.
Phase 2: Investigation
Goal: Gather evidence systematically, guided by language-specific techniques.
2.1 Load Language Reference
Detect the primary language of the bug's context and load the appropriate reference:
| Language | Reference | |----------|-----------| | Python | See references/python-debugging.md | | TypeScript / JavaScript | See references/typescript-debugging.md | | Other / Multiple | See the General Debugging Reference below |
Always also use the general debugging reference as a supplement when using a language-specific reference.
General Debugging Reference
Language-agnostic debugging strategies, systematic investigation methods, and common bug categories.
Systematic Debugging Methods
Binary Search for Bugs — Narrow the problem space by half at each step:
- Identify the full code path from input to incorrect output
- Place a diagnostic check at the midpoint
- Determine which half contains the bug
- Repeat in the failing half until the exact location is found
Works for: data transformation pipelines, middleware chains, multi-step processes.
Git Bisect — Automate binary search through commit history:
git bisect start
git bisect bad # current commit is broken
git bisect good <known-good-sha> # this commit was working
git bisect run <test-command> # returns 0 = good, non-0 = bad
git bisect reset # when done
Best for: regressions where you know "it used to work."
Delta Debugging — Minimize the input that triggers the bug:
- Start with the full failing input
- Remove half the input — does it still fail?
- If yes, keep the smaller input and repeat
- If no, restore and try removing the other half
- Continue until you find the minimal failing case
Rubber Duck Debugging — Explain the problem step by step:
- State what the code is supposed to do
- Walk through the actual execution, line by line
- At each step, explain what the state should be vs. what it is
- The discrepancy often reveals itself during the explanation
5 Whys — Drill past symptoms to root causes:
Bug: Users see a 500 error on checkout
Why? → The payment API call throws a timeout
Why? → The request takes >30 seconds
Why? → The order total calculation is O(n^2)
Why? → It recalculates item prices for each item pair
Why? → The discount logic compares every item against every other item
Root cause: Quadratic discount calculation algorithm
Reading Stack Traces
| Element | What It Tells You | |---------|-------------------| | Error type/name | Category of failure (null access, type mismatch, etc.) | | Error message | Specific details about what went wrong | | File path + line number | Where the error was thrown | | Function/method name | What was executing when it failed | | Frame ordering | The call chain that led to the error |
Investigation strategy:
- Read the error message first — it often contains the key clue
- Find your code in the trace (skip framework/library frames)
- Read the immediate caller — what arguments were passed?
- Check the state at that point — are variables what you expect?
- Trace backwards from the error to where the data originated
Bug Categories
- Off-by-One Errors: Check
<vs<=, 0-based vs 1-based indexing, inclusive vs exclusive ranges, empty collection edge case - Null/Undefined/None Errors: Check uninitialized variables, missing return values, optional fields without guards, API responses with unexpected nulls
- Race Conditions: Check shared mutable state, missing locks, read-then-write without atomicity, callback ordering assumptions
- Resource Leaks: Check unclosed file handles, unreturned database connections, event listeners not removed, timers not cleared
- State Corruption: Check mutation of shared objects, missing deep copies, partial updates, cache invalidation, global state modified by multiple paths
Diagnostic Logging Strategy
Log at decision points and data boundaries:
[ENTRY] function_name called with: key_arg=value
[BRANCH] taking path X because condition=value
[DATA] received from external: summary_of_data
[EXIT] function_name returning: summary_of_result
Investigation Checklist
Before proposing a fix, verify:
- Can you reproduce the bug reliably?
- What is the expected behavior vs actual behavior?
- Have you identified the specific line(s) causing the issue?
- Do you understand WHY those lines produce the wrong result?
- Is this the root cause, or a symptom