Debug
The expensive failure mode is changing code until the symptom disappears. That does not
remove the bug; it moves it. You are done when you can explain the mechanism and
turn the failure on and off on demand.
1. Reproduce — a bug you cannot trigger, you cannot fix
- Get the exact command, input, and environment. Run it yourself under
.claude/tools/watch.sh so a hang is killed with a reason (exit 124), not waited on.
- Record the real, complete error: the message, the stack, the exit code. Not a summary.
- Cannot reproduce? That is the finding. Say so and collect what is missing —
version, platform, data, timing, concurrency. Do not "fix" an unreproduced bug.
- Flaky? Run it 20 times and report the rate (
3/20). A rate is a fact; "sometimes" is
not, and the rate is how you will know you fixed it.
2. Narrow — halve the search space, don't tour it
Pick the cheapest axis available and bisect it:
- In time —
git bisect run <cmd> when it used to work. This is the single
highest-value debugging tool and it is consistently the one skipped.
- In input — delete half the input; still fails? delete half again. Land on the
smallest failing case and keep it, it becomes the test.
- In code — stub, short-circuit or comment out half the path. Which half keeps it?
- In environment — clean checkout, empty cache, the other machine, the CI container.
State what you eliminated at each step. Narrowing without recording the eliminations
means re-walking the same ground later.
3. Prove the mechanism
Form one hypothesis, phrased so it can be wrong: "X is null here because Y returns
early when the cache is cold." Then instrument to confirm or kill it.
- Print or log the actual values at the boundary — the input, the return, the state.
Assumed values are where bugs hide;
rg for the assignment rather than guessing.
- Reach for the real instrument when the cheap one stalls: a debugger and a watchpoint,
strace/dtruss, -fsanitize=address,undefined, valgrind,
go test -race, RUST_BACKTRACE=1, the browser console via the browser-testing
skill for anything rendered.
- A hypothesis you did not confirm is not the cause. UNKNOWN = FAIL
(
rules/prompt-contract.md). If the evidence kills your hypothesis, say so and form
the next one — do not fix the thing you happened to be looking at.
The usual suspects, when you are stuck
Uninitialized or stale state · an off-by-one or an inverted condition · a silently
swallowed error · a race or an ordering assumption · an encoding, timezone or locale
difference · a cache serving something stale · a version skew between local and CI ·
a shared fixture mutated by another test · a resource never released.
4. Write the failing test first
Before the fix, turn the smallest failing case from step 2 into a test in the project's
framework (.claude/tools/facts.sh detects it; rules/test-frameworks.md). Run it and
watch it fail for the right reason — a test that passes before the fix is testing
something else.
This is the RED step of agents/builder.md; the fix is GREEN.
5. Fix the cause, once
- Fix the mechanism you proved, not the symptom you saw. If the cause is a class of
bug, fix the class — one helper that escapes correctly beats twelve call sites
(
rules/library-first.md).
- Minimum change that makes the test pass (
rules/minimalism-ladder.md). Do not
refactor in the same commit.
- Touches anything irreversible, security-sensitive, schema-level or concurrent? Route
the fix through the
devil first (rules/risk.md).
- Then re-run the whole suite plus
.claude/tools/quality.sh — a fix that breaks
something else is not a fix.
6. Report
- Symptom — the exact error and the command that produced it.
- Cause — the mechanism, at
file:line, in one sentence.
- Evidence — what proved it, and what you eliminated on the way.
- Fix — what changed and why that is the cause, not the symptom.
- Proof — the new test failing before and passing after, pasted. For a flake, the
rate before and after (
3/20 → 0/20).
- Still unknown — anything you could not explain, named rather than skipped.
1---2name: debug3description: Find the actual cause of a failure instead of guessing at fixes. Reproduce, bisect to the smallest failing case, prove the mechanism, then fix once. Auto-triggers on: "why is this failing", "debug this", "this test is flaky", "it works locally", "fix this bug", "this crashes"4---56# Debug78The expensive failure mode is changing code until the symptom disappears. That does not9remove the bug; it moves it. You are done when you can **explain the mechanism** and10**turn the failure on and off on demand**.1112## 1. Reproduce — a bug you cannot trigger, you cannot fix1314- Get the exact command, input, and environment. Run it yourself under15 `.claude/tools/watch.sh` so a hang is killed with a reason (exit 124), not waited on.16- Record the real, complete error: the message, the stack, the exit code. Not a summary.17- **Cannot reproduce?** That is the finding. Say so and collect what is missing —18 version, platform, data, timing, concurrency. Do not "fix" an unreproduced bug.19- Flaky? Run it 20 times and report the rate (`3/20`). A rate is a fact; "sometimes" is20 not, and the rate is how you will know you fixed it.2122## 2. Narrow — halve the search space, don't tour it2324Pick the cheapest axis available and bisect it:2526- **In time** — `git bisect run <cmd>` when it used to work. This is the single27 highest-value debugging tool and it is consistently the one skipped.28- **In input** — delete half the input; still fails? delete half again. Land on the29 smallest failing case and keep it, it becomes the test.30- **In code** — stub, short-circuit or comment out half the path. Which half keeps it?31- **In environment** — clean checkout, empty cache, the other machine, the CI container.3233State what you eliminated at each step. Narrowing without recording the eliminations34means re-walking the same ground later.3536## 3. Prove the mechanism3738Form one hypothesis, phrased so it can be **wrong**: "X is null here because Y returns39early when the cache is cold." Then instrument to confirm or kill it.4041- Print or log the actual values at the boundary — the input, the return, the state.42 Assumed values are where bugs hide; `rg` for the assignment rather than guessing.43- Reach for the real instrument when the cheap one stalls: a debugger and a watchpoint,44 `strace`/`dtruss`, `-fsanitize=address,undefined`, `valgrind`,45 `go test -race`, `RUST_BACKTRACE=1`, the browser console via the `browser-testing`46 skill for anything rendered.47- **A hypothesis you did not confirm is not the cause.** UNKNOWN = FAIL48 (`rules/prompt-contract.md`). If the evidence kills your hypothesis, say so and form49 the next one — do not fix the thing you happened to be looking at.5051### The usual suspects, when you are stuck5253Uninitialized or stale state · an off-by-one or an inverted condition · a silently54swallowed error · a race or an ordering assumption · an encoding, timezone or locale55difference · a cache serving something stale · a version skew between local and CI ·56a shared fixture mutated by another test · a resource never released.5758## 4. Write the failing test first5960Before the fix, turn the smallest failing case from step 2 into a test in the project's61framework (`.claude/tools/facts.sh` detects it; `rules/test-frameworks.md`). Run it and62**watch it fail for the right reason** — a test that passes before the fix is testing63something else.6465This is the RED step of `agents/builder.md`; the fix is GREEN.6667## 5. Fix the cause, once6869- Fix the mechanism you proved, not the symptom you saw. If the cause is a class of70 bug, fix the class — one helper that escapes correctly beats twelve call sites71 (`rules/library-first.md`).72- Minimum change that makes the test pass (`rules/minimalism-ladder.md`). Do not73 refactor in the same commit.74- Touches anything irreversible, security-sensitive, schema-level or concurrent? Route75 the fix through the `devil` first (`rules/risk.md`).76- Then re-run the whole suite plus `.claude/tools/quality.sh` — a fix that breaks77 something else is not a fix.7879## 6. Report8081- **Symptom** — the exact error and the command that produced it.82- **Cause** — the mechanism, at `file:line`, in one sentence.83- **Evidence** — what proved it, and what you eliminated on the way.84- **Fix** — what changed and why that is the cause, not the symptom.85- **Proof** — the new test failing before and passing after, pasted. For a flake, the86 rate before and after (`3/20` → `0/20`).87- **Still unknown** — anything you could not explain, named rather than skipped.