Files
skills/skills/debug/SKILL.md
T
mathias 08c935ba8a
CI / Check / Test (push) Successful in 1s
CI / Tag release (push) Successful in 1s
chore(debug): fold in two techniques, delete systematic-debugging
superpowers-systematic-debugging restated debug's core (hypothesis before fix)
across 296 lines, with a fix-implementation phase that belongs to tdd and a
rationalization table that mostly repeats debug's own Anti-Patterns. Two things
in it were not restatement, and both are now in debug: instrument every
component boundary before hypothesising when a bug spans components, and treat
three failed fixes as evidence the mechanism was never identified rather than
as a reason to try a fourth.

Not deleted with it, contrary to the issue: find-polluter.sh and the three
technique files (root-cause-tracing, defense-in-depth, condition-based-waiting)
are real assets, one of them an actual tool. They move to debug/references/ with
a stated load trigger each, because a reference nobody knows when to open is
dead weight. The pressure-test transcripts and CREATION-LOG from the upstream
fork go; they document obra's process, not ours.

Pressure-tested per #19, and the RED phase refuted the issue's premise. Two
subagents without the skill got the multi-component scenario and the four-failed-
fixes scenario; both already instrumented boundaries unprompted, and the second
refused to write a fifth fix on its own initiative. So these sections are not
correcting a demonstrated gap in this model — they are insurance for weaker
models and for prompts less well-posed than a test scenario. Recorded as such
rather than claimed as a fix.

The GREEN run applied both sections correctly and then found four defects in
them, all fixed here: the boundary rule read as suspending Iron Law 2's 3-5
hypotheses; the trigger said "reduced" when fixes that change nothing are the
stronger signal; "stop proposing a fourth" read as licence to return no
hypotheses at all; and the references had no load triggers. Its remaining
findings are pre-existing and filed as #36.

41 skills. debug 146 -> 188 lines.

Closes #14
Bump-Type: minor
2026-08-29 11:33:36 +02:00

8.8 KiB

name, surfaces, summary, use_when, description
name surfaces summary use_when description
debug
claude-code
library
Hypothesis-first debugging discipline Any test failure or bug whose cause is not immediately obvious Systematic hypothesis-first debugging. Generate 3-5 ranked hypotheses with verification steps before suggesting any fix. Use when encountering a bug, test failure, or unexpected behavior.

Debug

Overview

Debugging is hypothesis generation, not fix generation. The first instinct — "try this and see if it works" — wastes time and teaches nothing. A disciplined debug session produces a small set of ranked, falsifiable hypotheses, each with a concrete verification command. The fix comes after one hypothesis is confirmed, not before.

Core principle: A hypothesis you cannot verify in one command is not a hypothesis — it is a guess.

When to Use

  • Any test failure whose cause is not immediately obvious
  • Any production error or unexpected behavior
  • Any bug report from a user or stakeholder
  • Before reaching for a debugger or adding print statements: form hypotheses first

Do not use for:

  • Compile errors with clear messages (just fix the typo)
  • Already-diagnosed bugs where you know the cause (go straight to TDD with a regression test)

Iron Laws

  1. Never suggest "try X and see what happens." Every hypothesis must have a specific expected outcome if correct.
  2. Generate 3-5 hypotheses, ordered by likelihood (most likely first). Fewer than 3 means you stopped thinking; more than 5 means you are not prioritizing.
  3. Diagnose only — do not fix in this skill. The fix happens in a separate TDD cycle (load tdd skill) once a hypothesis is confirmed.

When the bug spans components

A system with boundaries (CI → build → signing, API → service → database) hides the question "which component is wrong?" behind the question "why is the output wrong?". Specify, as a pre-step, what to log entering and leaving each boundary, plus whether config and environment actually propagated. One run of that narrows the search to a single component.

This does not suspend Iron Law 2. Still emit 3-5 hypotheses — the boundary spec comes first in the output, and each hypothesis says which boundary observation would kill it. What the pre-step forbids is hypotheses that quietly assume a component is the faulty one before any evidence says so.

When several fixes have already failed

Three or more plausible fixes that failed to eliminate a symptom is not a run of bad luck — it is evidence that nobody has identified the mechanism. (Fixes that changed nothing at all count double: they are stronger evidence of a misidentified mechanism than fixes that partially worked.) Each one has been narrowing a window rather than closing it, and a fourth guess buys another tenth of the rate plus a layer of interacting guards nobody can reason about.

Say that explicitly — the refusal is the finding, not a preamble to it. Then aim the hypotheses one level up: the premise those fixes share is itself the thing to falsify. This is not an exemption from the output format. It is the format, pointed at the assumption instead of at the symptom.

Process

Step 1: Read the failure

Before forming hypotheses:

  • Read the full error message and stack trace, not just the headline
  • Read the file where the failure originated, around the failing line
  • If the failure is from a test, read the test and the code under test
  • Identify the failure mode — what actually went wrong (e.g. "nil pointer dereference in goroutine spawned by handler") not just what the error says ("runtime error")

Step 2: Generate hypotheses

For each hypothesis, capture three things:

  • Mechanism: what specific code path or state would produce this exact failure
  • Verification: the single command or file inspection that confirms or denies it
  • Expected outcome if correct: the specific output you would see

Order by likelihood. The most likely cause is hypothesis 1.

Step 3: Output

Use this format:

HYPOTHESIS 1 (likelihood: high): <mechanism in one sentence>
VERIFY: <exact command or file:line to inspect>
EXPECTED IF CORRECT: <specific output, value, or condition>

HYPOTHESIS 2 (likelihood: medium): <mechanism>
VERIFY: <exact command>
EXPECTED IF CORRECT: <specific output>

[... up to 5 ...]

RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.

End with the recommendation, not a fix.

Worked Example

Failure:

--- FAIL: TestInvoiceParser_HandlesEmptyPDF (0.00s)
    parser_test.go:47: panic: runtime error: index out of range [0] with length 0

Output:

HYPOTHESIS 1 (likelihood: high): parser indexes into pages[0] before checking len(pages) > 0; empty PDFs produce a zero-page document
VERIFY: rg -n 'pages\[0\]' internal/parser/
EXPECTED IF CORRECT: at least one site reads pages[0] without a preceding length check

HYPOTHESIS 2 (likelihood: medium): the test fixture is a zero-byte file rather than a valid empty PDF; pdf library returns nil pages slice instead of empty
VERIFY: ls -la testdata/empty.pdf && file testdata/empty.pdf
EXPECTED IF CORRECT: file size 0 bytes or "data" rather than "PDF document"

HYPOTHESIS 3 (likelihood: low): a recently changed dependency reordered the page-extraction API; pages[0] now refers to metadata, not content
VERIFY: git log --oneline -10 -- go.sum | grep -i pdf
EXPECTED IF CORRECT: a pdf-library bump in the last few commits

RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.

Anti-Patterns

Anti-Pattern Why It Fails
"Maybe try restarting the service" Not a mechanism. Not falsifiable. Teaches nothing if it works.
"Could be a race condition" Mechanism without specifics. Which two operations race? On what state?
"Let me add some print statements and see" Skips hypothesis generation. Generates noise, not understanding.
Single hypothesis presented as fact If you are sure, write the regression test, do not run a debug skill.
Mixing hypotheses with fix suggestions The skill is diagnose-only. The fix is a TDD task on its hypothesis.

Brain MCP Integration

The brain holds prior debug sessions across the project. Use it to skip rediscovering known failure modes.

At debug start:

  • Run brain_query with the error message snippet + the package name. Past sessions may have logged identical or similar failures with their resolved hypotheses.

After a hypothesis is confirmed:

  • Run brain_write with the failure signature → confirmed mechanism. Future debug sessions on the same area get the answer immediately.

Never:

  • Run brain_write for an unconfirmed hypothesis. Speculation in the brain pollutes future queries.

Logging

Call session_log once at the end of every phase to record the outcome. Pass-rate is computed downstream by the /pass-rate HTTP endpoint, which treats pass as success, fail as failure, skip as neither.

At end of each phase:

  • session_log with {skill: "debug", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}

Phases for this skill: read-failure, generate-hypotheses, output

Status semantics:

  • pass — the phase's intended outcome was reached.
  • fail — the phase's intended outcome was NOT reached.
  • skip — phase was skipped intentionally.

Why this matters: the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data.

Mode 2 Routing Note

This skill produces high-volume mechanical output (hypothesis enumeration) and is a candidate for Mode 2 routing to a local model in the future. Until Plan 6 ships the routing pod, treat as Mode 1 only. The hypothesis format and discipline are identical regardless of which model generates them.

Cross-References

  • After a hypothesis is confirmed, load tdd skill — write a failing regression test that proves the bug exists, then fix it.
  • For test failures specifically caused by mock-vs-real divergence, also load tdd/references/testing-anti-patterns.md.
  • Load code-review skill if the diagnosis surfaces a structural issue (god object, shotgun surgery) rather than a pointwise bug.

Techniques kept in debug/references/, inherited from the deleted superpowers-systematic-debugging (#14):

  • root-cause-tracing.md — load when the failure surfaces far from its trigger and the stack is the only thread back to it.
  • defense-in-depth.md — load after a root cause is confirmed, to decide which layers should have caught it.
  • condition-based-waiting.md — load whenever a timeout appears anywhere in the report, including as one of the fixes already tried. A bumped timeout is a symptom.
  • find-polluter.sh — run when a test passes alone and fails in the suite, or when something in the tree appears that no test admits to creating.