| name | whyspec-debug |
| description | Use when encountering any bug, test failure, or unexpected behavior — before proposing fixes. |
| argument-hint | <bug-description-or-change-name> |
WhySpec Debug — Scientific Investigation
Debug systematically. No fix without root cause.
The investigation is automatically saved as a context file when resolved.
Input: A bug description, error message, or change name for an existing debug session.
Iron Law
NO FIX WITHOUT VERIFIED ROOT CAUSE. Guessing at fixes creates more bugs than it solves. A wrong diagnosis leads to a wrong fix that masks the real problem.
Red Flags — If You're Thinking This, STOP
- "The fix is obvious, I'll just apply it" → If it's obvious, verification takes 10 seconds. Do it.
- "I'll try this fix and see if it works" → That's guess-and-check, not debugging. Form a hypothesis first.
- "This is too simple to need the full process" → Simple bugs have root causes too. The process is fast for simple bugs.
- "I already know what's wrong from the stack trace" → The stack trace shows WHERE, not WHY. Investigate the WHY.
- "Let me just add some logging and see" → Logging is a test for a hypothesis. What's your hypothesis?
Rationalization Table
| If you catch yourself thinking... | Reality |
|---|
| "The fix is obvious, skip investigation" | Obvious fixes have root causes too. Verify in 30 seconds. |
| "It's just a typo/config issue" | Confirm it. Read the code. Don't assume. |
| "I'll just try this quick fix first" | The first fix sets the pattern. Do it right from the start. |
| "No time for full investigation" | Systematic debugging is FASTER than guess-and-check thrashing. |
| "I already know what's wrong" | Then verification should take 10 seconds. Do it anyway. |
| "It works on my machine" | That's a symptom, not a diagnosis. Find the environmental difference. |
| "The error message tells me exactly what's wrong" | Error messages describe symptoms. Root causes are upstream. |
| "Let me just revert the last change" | Revert is a workaround, not a fix. Why did the change break things? |
Tools
| Tool | When to use | When NOT to use |
|---|
| Grep | Search for error messages, function names, patterns in code | Don't grep before forming hypotheses — symptoms first |
| Read | Read suspect files, stack trace locations, config files | Don't read unrelated files — stay focused on hypotheses |
| Bash | Run tests to reproduce, git log/git diff to find trigger commits, execute test commands for hypotheses | Don't run destructive commands or modify production data |
| Glob | Find files by pattern when error references unknown paths | Don't glob the entire repo — scope to suspect areas |
| Write | Write/update debug.md (investigation state) and ctx_.md (final capture) | Don't write fix code until root cause is verified |
| WebSearch | ONLY for: error messages with no codebase matches, library changelogs, CVE lookups | Never search web for: "how to debug X", generic solutions, or before investigating the codebase |
| AskUserQuestion | Escalation ONLY — after 2 rounds of failed hypotheses, or when root cause is outside codebase | Don't ask before investigating. The codebase has the answers. |
Codebase First, Web Never-First
Read the codebase BEFORE considering web search. Web search is justified ONLY when:
- Error message has zero matches in the codebase or git history
- Library version changelog needed (breaking changes between versions)
- Security advisory lookup (CVEs)
- Stack trace references internal framework code you can't read locally
Never search the web for: "how to fix X", architecture decisions, or generic debugging advice.
Step 0: Team Knowledge Search
Before investigating, check if someone has reasoned about this domain before:
whyspec search --json "<keywords from bug description>"
If results exist:
- Display relevant titles and key decisions from past investigations
- Note past decisions that might inform the current bug (e.g., "The auth middleware was deliberately changed in add-auth — this might explain the current session bug")
If no results: note "No prior context found" and continue.
This takes seconds. It prevents re-investigating solved problems.
Step 1: Symptoms Gathering
Create the debug session:
whyspec debug --json "<bug-name>"
Parse the JSON response:
path: Debug session directory
template: debug.md template structure
related_contexts: Past contexts in the same domain
Gather symptoms — investigate the codebase directly. Only ask the user if you genuinely can't find the information yourself:
| Symptom | What to capture |
|---|
| Expected behavior | What SHOULD happen |
| Actual behavior | What ACTUALLY happens |
| Error messages | Exact text, stack traces, error codes |
| Reproduction steps | Minimal sequence to trigger the bug |
| Timeline | When it started, what changed recently |
| Scope | Who is affected, how often, which environments |
## Symptoms
**Expected:** POST /api/users returns 201 with user object
**Actual:** Returns 500 with "Cannot read properties of undefined (reading 'email')"
**Error:** TypeError at src/handlers/users.ts:47 — `req.body.email` is undefined
**Stack trace:**
```
TypeError: Cannot read properties of undefined (reading 'email')
at createUser (src/handlers/users.ts:47:28)
at Layer.handle (node_modules/express/lib/router/layer.js:95:5)
```
**Reproduction:** `curl -X POST localhost:3000/api/users -H "Content-Type: application/json" -d '{"name":"test"}'`
**Timeline:** Started after commit a1b2c3d (merged express-validator upgrade, Apr 8)
**Scope:** All POST endpoints with body parsing, not just /users. GET endpoints unaffected.
Why good: Exact error with file:line, exact reproduction command,
identified the trigger commit, and noticed the scope is broader than reported.
## Symptoms
**Expected:** API should work
**Actual:** Getting 500 errors
**Error:** Server error
Why bad: Vague symptoms lead to vague hypotheses. No file, no line,
no reproduction steps, no timeline.
Write debug.md immediately to <path>/debug.md. It persists across context resets.
Step 2: Hypothesis Formation
Form 3 or more falsifiable hypotheses:
### H1: express-validator upgrade broke body parsing middleware order
- **Test:** `git diff a1b2c3d -- src/app.ts` — check if middleware registration order changed
- **Disproof:** If middleware order is identical pre/post upgrade, this is wrong
- **Status:** UNTESTED
- **Likelihood:** HIGH (scope matches — all POST endpoints affected, timing matches upgrade)
H2: express-validator v7 changed req.body population timing
- Test: Add
console.log(req.body) before and after validation middleware, compare output
- Disproof: If req.body is populated before validation in both versions, this is wrong
- Status: UNTESTED
- Likelihood: MEDIUM (v7 changelog mentions "async validation" changes)
H3: Content-Type header handling changed in the upgrade
- Test: Send request with
Content-Type: application/json; charset=utf-8 — does it parse?
- Disproof: If extended Content-Type works the same in both versions, this is wrong
- Status: UNTESTED
- Likelihood: LOW (but worth testing — charset handling has caused issues before)
Why good: Each hypothesis is specific, testable, and falsifiable.
They target different root causes. Likelihood is justified with evidence.
### H1: Something is wrong with the API
- **Test:** Check the API
- **Disproof:** If the API works
### H2: Maybe a dependency issue
- **Test:** Check dependencies
Why bad: Not specific enough to test. "Check the API" is not a concrete action.
Rank by likelihood. Test the most likely first. Update debug.md before proceeding.
Step 3: Hypothesis Testing
Test each hypothesis one at a time, sequentially:
- Execute the test described in the hypothesis
- Record evidence — exact output, logs, observed behavior
- Evaluate — support, refute, or inconclusive?
- Update status:
CONFIRMED, DISPROVED, or INCONCLUSIVE
- Update debug.md immediately
Rules:
- One hypothesis at a time — never test multiple simultaneously
- Max 3 tests per hypothesis — if inconclusive after 3, mark INCONCLUSIVE and move on
- Preserve the crime scene — record current state before modifying suspect code
- Update debug.md after each test — don't batch
If ALL hypotheses are disproved:
- Form new hypotheses based on what evidence revealed
- If stuck after a second round, escalate to the user
Step 4: Root Cause Verification
Before proposing ANY fix:
- State the root cause clearly and specifically
- Explain the causal chain: [trigger] → [mechanism] → [symptom]
- Verify predictive power: can you predict the symptom from the cause?
## Root Cause
**Cause:** express-validator v7 switched to async validation, body parsing
now completes AFTER route handler starts executing
**Causal chain:** express-validator upgrade → async body parsing → req.body
undefined when handler reads it synchronously → TypeError
**Verified by:** Adding `await` before validation resolved the issue in test
**Confidence:** HIGH
| Confidence | Criteria | Action |
|---|
| HIGH | Reliable reproduction, clear causal chain | Proceed to fix |
| MEDIUM | Strong evidence, some uncertainty | Proceed with caution, note risks |
| LOW | Circumstantial evidence | Escalate — do NOT fix |
Step 5: Fix + Auto-Capture
Once root cause is verified (HIGH or MEDIUM confidence):
-
Implement the minimal fix — fix the bug, don't refactor surrounding code
-
Verify the fix — run reproduction steps again, run test suite
-
Update debug.md — add fix details and prevention measures:
## Fix
**Change:** Added `await` before express-validator `validationResult()` calls
**Files:** src/middleware/validate.ts (3 lines changed)
**Verification:** `npm test` — 47 pass, 0 fail. Manual curl test returns 201.
## Prevention
- Added eslint rule for async validation middleware
- Added integration test: POST with body → verify req.body populated
- Updated UPGRADE.md with express-validator v7 migration notes
-
Commit atomically with root cause in message
-
Auto-capture reasoning:
whyspec capture --json "<bug-name>"
Write <path>/ctx_<id>.md with the full investigation story.
-
Show summary:
## Debug Complete: <bug-name>
Root cause: [one-line summary]
Fix: [what was changed]
Context: ctx_<id>.md
Investigation:
Hypotheses tested: N (M confirmed, P disproved)
Evidence entries: N
Past contexts referenced: N
View full investigation: /whyspec-show <bug-name>
Resuming an Investigation
If debug.md already exists for a change:
- Read debug.md
- Check Status and resume from the appropriate step
- Announce: "Resuming debug session: — Status: "
Escalation Rules
| Trigger | Action |
|---|
| All hypotheses disproved (2 rounds) | Present full evidence, ask for new direction |
| Cannot reproduce | Document symptoms, ask for environment details |
| Root cause outside codebase | Document findings, suggest infrastructure investigation |
| Confidence is LOW | Present evidence, explain uncertainty, do NOT fix |
| Fix introduces significant risk | Present fix + risk assessment, ask for approval |
| 3 failed fix attempts | Stop. Present what was tried. Ask for help. |
Never silently give up. If stuck, present evidence and ask.
Guardrails
- No fix without root cause — the Iron Law is non-negotiable
- Max 3 tests per hypothesis — escalate if inconclusive
- Always capture reasoning — every debug session produces both debug.md AND ctx_.md
- Write debug.md incrementally — update after EVERY step, not at the end
- Don't skip team knowledge — always run Step 0
- Test one hypothesis at a time — sequential testing produces clean evidence
- Preserve evidence — record state before modifying suspect code
- Minimal fixes only — fix the bug, don't refactor