Systematic debugging — investigate, hypothesize, fix, verify. No fixes without root cause.
Scanned 5/27/2026
Install via CLI
openskills install iamvonpasion/hashb---
description: Systematic debugging — investigate, hypothesize, fix, verify. No fixes without root cause.
---
# Investigate
**Iron Law: NO FIXES WITHOUT ROOT CAUSE.**
Fixing symptoms creates whack-a-mole debugging. Every fix that doesn't
address root cause makes the next bug harder to find.
---
## Presentation Rules
Follow the shared formatting rules in `skills/shared/formatting.md`.
1. **Progress indicator** — every output starts with:
```
/fix ════════════════════════════════════════════════════════════
▸ Phase 0 Preflight & Tracker
○ Phase 1 Gather Evidence
○ Phase 2 Test Hypothesis
○ Phase 3 Fix
○ Phase 4 Verify & Report
════════════════════════════════════════════════════════════════
```
Update `▸` (current), `✓` (done), `○` (pending) as phases progress.
Completed phases show a status note (e.g., `✓ hypothesis confirmed`, `✓ 1 file changed`).
---
## Phase 0: Preflight & Tracker
Run `skills/shared/preflight.md` for branch/base detection.
Run `skills/shared/tracker.md` §Tracker Detection if `TRACKER_TYPE` is not yet
cached. Then run the §Issue Resolution Block to resolve `TASK_ISSUE` from the
user's invocation message, current branch name, or conversation context.
### Re-entry Detection
Before starting Phase 1, check for prior state — the user may be resuming
a bugfix from a prior session.
**Check 1 — Local state file:** Read `.fix-state-{branch}.json` if it exists.
If found, this is a resumed investigation. Report the prior state (strikes,
phase, bug description) and ask whether to continue or start fresh.
**Check 2 — Tracker labels (when `TASK_ISSUE` is resolved):** Query the
issue's labels to determine chain position:
```bash
if [ "$TRACKER_TYPE" = "github-issues" ] && [ -n "$TASK_ISSUE" ]; then
LABELS=$(gh issue view "$TASK_ISSUE" --json labels --jq '.labels[].name' 2>/dev/null)
fi
```
| Labels found | Meaning | Action |
|---|---|---|
| `hashb:fix` present | Fix already completed in a prior session | Skip `/fix` — suggest `/review` or `/ship` as next chain step |
| `status:investigating` (no `hashb:fix`) | Investigation started but not finished | Resume from Phase 1 or 2 (check state file for strikes) |
| `status:dev` (no `hashb:fix`) | Fix implementation started but not verified | Resume from Phase 3 or 4 |
| `hashb:fix` + `hashb:review` (issue open) | Fix and review done, not shipped | Skip `/fix` and `/review` — suggest `/ship` |
| No hashb labels | Fresh bug — first run | Proceed normally to Phase 1 |
**Chain routing on re-entry:** When tracker labels show the fix is already done,
present the chain status and route forward instead of re-fixing:
```
▸ RE-ENTRY — issue #N already has [fix ✓] from prior session
Chain status:
✓ /fix {root cause summary from issue or commit}
○ /review not yet run
○ /ship not yet run
Next: /hashb:review (recommended — resume chain)
```
If labels show no prior state, proceed with Status Swap to
`status:investigating` using `skills/shared/tracker.md` §Status Swap Block.
---
## Phase 1: Gather Evidence
Collect before hypothesizing. Do not guess. No file edits or code
changes until the root cause hypothesis is written — reading and
grepping are the only tools in Phase 1. If invoked mid-conversation
after prior fix attempts, discard those attempts as noise and start
evidence gathering fresh from the original symptom.
**Read the symptoms:** Error messages, stack traces, reproduction steps.
If the user hasn't provided enough context, ask ONE focused question.
**Trace the code path:** Follow execution from symptom back to potential
causes. Grep all references, read the logic.
**Check recent changes:**
```bash
git log --oneline -20 -- <affected-files>
```
Was this working before? If so, the root cause is in the diff.
**Design source first (if visual/UI bug):** When the symptom is a visual
mismatch — wrong color, spacing, layout, or appearance — the design file
is the primary evidence source, not a screenshot.
1. Locate the design spec: Figma link, design HTML export, token file,
or reference in the spec/ticket. If the user provided only a
screenshot, ask: "Is there a design file or Figma link for this?"
2. Read the design spec BEFORE reading the component code.
Extract concrete values: colors, spacing, font sizes, radii, states.
3. A screenshot is a symptom report. A design file is the spec.
Fix to the spec, not to the screenshot.
**Trace existing implementations (if visual/UI bug):** Before fixing a
visual element, find where the same treatment is already applied
correctly elsewhere in the codebase.
```bash
# Find components using the same visual pattern
grep -r "bg-heading\|variant.*primary" --include="*.tsx" --include="*.css"
```
If the same visual treatment (button style, avatar, badge, indicator)
exists elsewhere and looks correct, that implementation is your
reference — copy the approach, don't reinvent it.
**Check known issues:** Scan `TODOS.md` (root index + General section) and
every `specs/*.todos.md` referenced by the index, plus git log, for prior
fixes in the same area. Recurring bugs in the same files = architectural smell.
**Graph-Aware Tracing (if graphify MCP is available):**
If the consumer's Project Profile lists `graphify`, query it to
accelerate evidence gathering:
- Trace callers and dependents of the symptomatic function to find the
full call chain from entry point to failure site
- Identify which tests cover the affected code paths — if they're passing,
the bug may be in an untested path
- Map the blast radius of the suspected root cause before fixing
If graphify is not available, trace manually via grep and file reads.
**Reproduce:** Can you trigger it deterministically? If not, gather more
evidence before proceeding.
**Output:** "Root cause hypothesis: ..." — a specific, testable claim
about what is wrong and why.
---
## Phase 2: Test the Hypothesis
Before writing ANY fix, verify your hypothesis with instrumentation.
### Instrumentation Gate
Determine whether instrumentation is needed based on the bug type:
| Bug type | Instrument first? | Example |
|----------|-------------------|---------|
| **Obvious** — root cause visible in code | No — fix directly | Typo, missing import, wrong variable, config error |
| **Non-obvious** — hypothesis needs verification | **Yes — mandatory** | State bugs, race conditions, hydration, lifecycle, async timing, silent failures |
**If instrumentation is needed:**
1. Add `console.log` / `logger.debug` / breakpoint at the suspected root cause
2. Add logging at the entry and exit of the affected code path
3. Run the reproduction and **paste the diagnostic output**
4. Only proceed to a fix if the output confirms your hypothesis
If you cannot reproduce with instrumentation, say so and present options
to the user. Do not guess.
> **Why this matters:** Retro evidence shows that skipping instrumentation
> on non-obvious bugs leads to 3-6x fix attempts. Reading logs is faster
> than reading diffs.
**Confirm it:** Does the evidence (code reading or diagnostic output) match your hypothesis?
### 5 Whys Analysis
Once the immediate cause is identified, apply 5 Whys to reach the systemic
root cause. Shallow fixes ("the variable was null") create recurring bugs.
Deep fixes ("the schema allows null where it shouldn't") prevent entire classes
of bugs.
```
WHY #1: Why did {symptom} happen?
→ {immediate cause}
WHY #2: Why did {immediate cause} happen?
→ {deeper cause}
WHY #3: Why did {deeper cause} happen?
→ {process/design cause}
WHY #4: Why did {process/design cause} happen?
→ {systemic cause}
WHY #5: Why did {systemic cause} happen?
→ {root cause — fix HERE}
```
**Rules:**
- Stop early if you reach a root cause before 5 — don't force it
- Each "why" must be supported by evidence, not speculation
- If a "why" has multiple answers, branch and investigate each
- The final "why" should point to something **preventable** — a missing
validation, a design gap, a process failure. If it points to "human error,"
go one level deeper: why did the system allow the error?
**Output:** State the root cause chain:
```
ROOT CAUSE CHAIN ────────────────────────────────────────────────
Symptom {what the user saw}
│
Why #1 {immediate cause}
│
Why #2 {deeper cause}
│
Root cause {the thing to fix}
│
Fix target {code/process/rule change that prevents recurrence}
─────────────────────────────────────────────────────────────────
```
**If wrong:** Return to Phase 1. Gather more evidence. Do not guess.
**Check if it matches a known pattern:**
| Pattern | Signature | Where to look |
|---------|-----------|---------------|
| Race condition | Intermittent, timing-dependent | Concurrent access to shared state |
| Nil propagation | NoMethodError, TypeError | Missing guards on optional values |
| State corruption | Inconsistent data, partial updates | Transactions, callbacks, hooks |
| Integration failure | Timeout, unexpected response | External API calls, service boundaries |
| Config drift | Works locally, fails elsewhere | Env vars, feature flags, DB state |
| Stale cache | Shows old data, fixes on clear | Redis, CDN, browser cache |
| Visual mismatch | Wrong color, size, spacing, layout vs. design | Design tokens, theme files, component variants, CSS override chain |
| Interaction mismatch | Wrong hover/focus/active/disabled state | Event handlers, CSS pseudo-classes, state-driven class logic |
**3-strike rule (HARD STOP — not a suggestion):**
If 3 hypotheses fail, you **MUST** stop. Do not attempt a 4th fix.
Do not continue investigating silently. Present this to the user and WAIT:
```
■ 3-STRIKE STOP ─────────────────────────────────────────────────
Hypotheses tested:
1. {hypothesis} — {why it failed}
2. {hypothesis} — {why it failed}
3. {hypothesis} — {why it failed}
Options:
A) New lead — {describe what's different this time}
B) Escalate — this needs domain knowledge I don't have
C) Instrument deeper — add more logging, reproduce again
─────────────────────────────────────────────────────────────────
```
> **Why this is a hard stop:** Retro evidence shows that blowing past
> 3 strikes leads to 6+ attempts, wasted commits, and trust erosion.
> Stopping at 3 and asking is always cheaper than guessing at 6.
### Strike Persistence
Persist strike state and chain context to disk so progress survives context
compaction and session boundaries:
**On each failed hypothesis**, write `.fix-state-{branch}.json`:
```json
{
"branch": "{branch}",
"bug": "{short description}",
"task_issue": 50,
"phase": "investigating",
"strikes": [
{"hypothesis": "...", "result": "disproved — ...", "timestamp": "..."}
],
"root_cause": null,
"status": "INVESTIGATING"
}
```
| Field | Purpose |
|-------|---------|
| `task_issue` | GitHub Issue number (or `null`) — enables re-entry without re-resolving |
| `phase` | Current phase: `investigating`, `hypothesis`, `fixing`, `verifying` |
| `root_cause` | Root cause summary once confirmed (or `null`) — carries context across sessions |
| `strikes` | Failed hypothesis chain |
| `status` | `INVESTIGATING`, `FIXING`, `HARD_STOP`, `DONE` |
**Update the file at each phase transition:**
- Phase 1 complete → set `phase: "hypothesis"`
- Phase 2 (hypothesis confirmed) → set `phase: "fixing"`, `root_cause: "..."`, `status: "FIXING"`
- Phase 3 complete → set `phase: "verifying"`
- Phase 4 (DONE) → set `status: "DONE"` (file cleaned after badge/handoff)
On 3rd strike, set `"status": "HARD_STOP"`.
**At Phase 0 or Phase 2 start**, read `.fix-state-{branch}.json` if it exists:
- If `status` is `HARD_STOP` — present the 3-strike stop immediately.
- If `status` is `INVESTIGATING` with strikes — resume from recorded position.
- If `status` is `FIXING` — root cause is already confirmed, resume from Phase 3.
- If `task_issue` is set — restore `TASK_ISSUE` without re-running Issue Resolution.
**On resolution:** `rm -f ".fix-state-${BRANCH}.json"`
---
## Phase 3: Fix
Once root cause is **confirmed** (not suspected — confirmed):
### Branch Guard
Before writing any code, ensure you're on a feature branch:
```bash
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "⚠ ON PROTECTED BRANCH — creating fix branch"
SLUG=$(echo "{short-bug-description}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]')
if [ -n "$TASK_ISSUE" ]; then
git checkout -b "fix/#${TASK_ISSUE}-${SLUG}"
else
git checkout -b "fix/$SLUG"
fi
fi
```
> **Never write fixes directly on main/master.** If the user explicitly
> says to commit to main, warn them and proceed only with confirmation.
### Status Swap — Dev
When the fix implementation begins (after hypothesis confirmed), swap
the issue status to `status:dev` using `skills/shared/tracker.md`
§Status Swap Block. This distinguishes active coding from investigation.
1. **Write a regression test FIRST — before writing the fix.**
The test must target the exact bug scenario from the root cause chain.
2. **RED: Run the test. It MUST fail.** Paste the failure output.
If it passes without a fix, the test is wrong — it's not testing the bug.
Rewrite it until it fails for the right reason.
3. **Fix the root cause from the 5 Whys chain, not the symptom.** Smallest
change that eliminates the actual problem at the deepest actionable level.
Minimal diff — fewest files, fewest lines. Resist refactoring adjacent
code during a bug fix.
4. **GREEN: Run the test again. It MUST pass.** Paste the passing output.
This is the proof the fix works. Without RED then GREEN on the same test,
you have no proof — only a claim.
5. **Run the full test suite.** Paste output. No regressions.
**Blast radius check:** If fix touches >5 files, stop and ask:
```
This fix touches N files — large for a bug fix.
A) Proceed — root cause genuinely spans these files
B) Split — fix critical path now, defer the rest
C) Rethink — maybe a more targeted approach exists
```
---
## Phase 4: Verify & Report
**Reproduce the original bug and confirm it's fixed.** Not optional.
Run the test suite, paste output.
```
✓ DEBUG REPORT ──────────────────────────────────────────────────
Symptom {what the user observed}
Root cause {what was actually wrong}
Fix {what changed — file:line refs}
Evidence {test output or repro showing fix works}
Regression test {file:line of new test}
Related {TODOS, prior bugs, architectural notes}
Task #{TASK_ISSUE} (if resolved) | — (no tracker)
Status DONE | DONE_WITH_CONCERNS | BLOCKED
Next: /hashb:{recommended-per-status} (recommended — {status phrase})
─────────────────────────────────────────────────────────────────
```
**Tracker badge label** — if `TRACKER_TYPE=github-issues` (see
`skills/shared/tracker.md` §Badge Label Block), also add the `hashb:fix` label
to the issue on DONE or DONE_WITH_CONCERNS status. Skip on BLOCKED (no
verified fix to badge). The badge marks the fix as verified for cross-session
visibility.
**TODOS badge append** — if the task is tracked in a TODOS file (root
`TODOS.md` or `specs/*.todos.md`), append `[fix ✓]` on the continuation
line of the task entry:
```markdown
- [ ] P1 [M] #50 Dashboard shows stale data
[fix ✓]
```
This enables `/decompose` re-entry to show fix completion in the progress
table, and new sessions to see which bugs have been verified.
**Handoff enrichment** — when `TASK_ISSUE` is resolved, include `Task: #N`
and `Refs: #N` in the handoff so downstream `/review` and `/ship` inherit
the issue reference. Commit messages for the fix should include `Refs: #N`
in the footer (per `rules/git.md` bug fix template).
**State file cleanup** — after badge and handoff are emitted, clean up the
transient state file: `rm -f ".fix-state-${BRANCH}.json"`.
### Next Step
| Status | Next Skill | Why |
|--------|-----------|-----|
| DONE | `/review` (if not yet reviewed) or `/ship` | Verify fix in context |
| DONE_WITH_CONCERNS | `/review` with concerns flagged | Verify, watch for edge cases |
| BLOCKED | Escalate to user | Root cause unclear after investigation |
**Autonomous mode:** If running inside a recipe chain:
- DONE → auto-proceed to `/review` if this is the first fix, or `/ship` if already reviewed
- DONE_WITH_CONCERNS → auto-proceed to `/review` with concerns flagged
- BLOCKED → stop the chain, present investigation to user
---
## Red Flags — Slow Down If You See These
- **"Quick fix for now"** — there is no "for now." Fix it right or escalate.
- **Proposing a fix before tracing data flow** — you're guessing.
- **Each fix reveals a new problem elsewhere** — wrong layer, not wrong code.
- **Can't reproduce** — don't ship a fix you can't verify.
- **3+ failed attempts** — question the architecture, not your luck.
- **Touching library persistence/lifecycle internals** — if the fix involves
changing how a library manages state, storage, hydration, or lifecycle
(Zustand persist, React Query cache, auth token storage, ORM hooks, etc.),
**STOP and run `/research` first.** Read the library source code or query
Context7 for the internal behavior before changing it. "Simple" storage
swaps can cascade into complex bugs when the library's internal assumptions
are violated.
- **Fixing visual appearance without a design reference** — if adjusting
colors/spacing by eyeballing a screenshot, you're guessing. Get the
design file or find an existing component that implements the treatment.
- **Same fix attempted twice with different values** — if you've tried
two different colors/sizes and neither matches, you don't have the
spec. Stop and ask for the design source.
---
## Completion Status
- **DONE** — root cause found, fix applied, regression test passes, suite green
- **DONE_WITH_CONCERNS** — fixed but can't fully verify (intermittent, needs staging)
- **BLOCKED** — root cause unclear after investigation, escalated
No comments yet. Be the first to comment!