bugfix · git:20260710.47b8db6 · 2026-07-10 · sha256 cefb7c8c2dba6b94
bugfix git:20260710.47b8db6A
Immutable. This exact content is served forever at /api/v1/blob/cefb7c8c2dba6b94.
---
name: bugfix
description: >-
Lightweight bug-fixing loop — reproduce → root-cause → fix test-first →
verify — deliberately separate from the heavyweight feature workflow so
small fixes don't drag a spec-and-plan process behind them. Demands a
deterministic reproduction before any code changes, a stated root-cause
hypothesis confirmed with evidence (never a shotgun fix), a failing test
that captures the bug, and the original repro re-run as proof. Keeps a
one-line-per-bug log in out/dev/bugfix-log.md and checks it first, so
recurring bugs get recognized instead of re-diagnosed. Use when the user
reports something broken, failing, throwing, or flaky, or asks to fix a
bug — e.g. "/bugfix login 500s on empty password", "fix this error",
"this test is flaky", "why is X crashing". If the fix turns out to need
real design work, hand off to feature; the shipping gate is done.
---
# bugfix — reproduce, root-cause, fix, prove
`/bugfix <symptom / error / failing thing>`
Answer one question: **why does this actually happen, and what's the minimal
fix that provably removes it?** The order is non-negotiable — reproduce
before diagnosing, diagnose before fixing, prove after fixing. A fix without
a confirmed root cause is a guess wearing a commit message.
## Step 0 — Check the log
Read `out/dev/bugfix-log.md` if it exists. A similar symptom fixed before is
the best diagnostic head start there is — past root causes cluster ("we've
had three timezone bugs in this module"). Mention any match. Also skim recent
`git log` — a bug that appeared recently usually shipped recently.
## Step 1 — Reproduce (before touching any code)
Turn the report into a **deterministic reproduction**: a command, a failing
test invocation, a curl, a click path — something that fails on demand and
will pass when the bug is dead.
- Capture the exact error output now; it's the baseline the fix is judged
against.
- **Can't reproduce it?** Stop. Report what was tried, what extra information
would help (versions, data, environment, timing), and ask. Do not fix code
you can't watch fail — "fixed" without a repro is unfalsifiable.
- Flaky bug? Reproduce the *flakiness* (run N times, note the failure rate)
so the fix can be judged the same way.
## Step 2 — Root-cause (hypothesis, then evidence)
Work from symptom back to cause; don't pattern-match a fix onto the error
message.
1. Read the failing path — the actual code, not just the stack trace.
2. **State a hypothesis out loud**: "X fails because Y, so Z should show W."
3. Confirm it with evidence: a log line, a debugger/print probe, a minimal
experiment that isolates the suspect. The hypothesis must *predict*
something you then observe.
4. Wrong? Say so, log what was ruled out, form the next hypothesis.
**Two dead hypotheses → step back**: re-read the repro, question an
assumption, or bring the user the map of what's been ruled out. Grinding
the same theory harder is not investigation.
**No shotgun fixes**: changing several things at once until the symptom stops
proves nothing and usually hides a second bug. One hypothesis, one change,
one observation.
## Step 3 — Fix, test-first
1. **Write the failing test that captures the bug** — it should fail for the
bug's exact reason, at the root-cause layer (unit test at the broken
function beats an end-to-end flake).
2. Apply the **minimal fix** for the root cause. Not the refactor the code
deserves, not the defensive `try/except` around the symptom — the cause.
3. Test green, then the **full suite** green (the fix must not buy one green
at the cost of another).
If the minimal fix keeps growing — touching many files, changing behavior,
demanding design decisions — stop and say so: this is a **feature** wearing a
bug costume. Hand off to `/feature` with the diagnosis as its input; the
root-cause work is not wasted, it's the spec's first paragraph.
## Step 4 — Prove and record
- Re-run the **original reproduction** from Step 1 — show before/after
output. This is the proof; the new test alone is not (it might test the
wrong thing).
- Quick blast-radius check: who else calls the changed code? Any caller
relying on the old (broken) behavior gets flagged, not silently re-broken.
- Append one line to `out/dev/bugfix-log.md`
(create it if missing; date via `date +%F`):
```markdown
| Date | Symptom | Root cause | Fix | Test |
|------|---------|-----------|-----|------|
| 2026-07-05 | login 500 on empty password | None reaches bcrypt in auth.py | guard + 400 | test_auth.py::test_empty_password |
```
- Commit per the repo's convention, with a message naming the root cause,
not just the symptom. For anything beyond a
trivial diff, offer the **done** gate before shipping. (In auto mode —
under the **autopilot** skill — the commit and the done gate run without
asking; anything that would need the user's ruling parks instead.)
## Guardrails
- **Repro first, always.** No reproduction → no code changes, no exceptions.
- **Cause, not symptom.** Never suppress the error (swallowed exception,
widened timeout, retry loop) unless the user explicitly chooses that
trade-off — and then log it as suppression, not a fix.
- **Never weaken tests.** Deleting/loosening an assertion to get green is a
new bug. An existing test blocking the fix is information for the user.
- **Minimal diff.** Improvements spotted along the way are mentioned, not
bundled — a bugfix diff should read as exactly one repair.
- **Honest log.** The bugfix log records what the cause *was*, even when
embarrassing — it's the cross-run memory that makes bug #3 in the same
module a 5-minute fix.