bug-fix · git:20260610.5405736 · 2026-06-10 · sha256 6be1a0f87a59de2c
bug-fix git:20260610.5405736A
Immutable. This exact content is served forever at /api/v1/blob/6be1a0f87a59de2c.
---
name: bug-fix
description: Use this skill when the user wants to fix a bug -- a deviation between current behavior and intended behavior in code that already exists. Triggers on "fix bug", "fix this bug", "diagnose and fix", "investigate this regression", "this is broken". Do NOT use for new features (use `new-spec`) or for refactors that don't fix incorrect behavior.
---
# Skill: bug-fix
Fix a defect in the smallest, most root-causing way. The discipline is
universal: reproduce before fixing, write the failing test first,
identify root vs symptom, minimum diff, commit body documents why.
## When to invoke
Even a one-line fix benefits from walking this discipline; it forces
the question "is this fixing the cause or hiding it?"
For multi-file changes that go beyond fixing one defect — refactors,
new features triggered by discovering the bug — stop and use
`new-spec` instead. This skill is for bug fixes, not opportunistic
restructuring.
## Procedure
1. **Reproduce first.** Don't write a fix until you have one of: a
failing test, documented manual reproduction steps that fail
reliably, or a captured error / stack trace / log signature. No
reproduction = no fix; you might be fixing the wrong thing.
2. **Write the failing test (red).** It should pin the *observable
contract being violated*, not the current implementation. Push
back on these failure modes:
- **Mock-shape assertion.** `expect(mock).toHaveBeenCalledWith(...)`
when the observable contract is a returned value or state
change. Test the contract, not the implementation. (See
`quality-engineer`'s "mock-shape assertions" check.)
- **Test passes for the wrong reason.** Run the test against the
unfixed code; confirm it fails *because of* the bug, not
because the setup is wrong.
3. **Identify root cause before writing the fix.** Write down a
one-line answer to each:
- **Where is the defect actually?** In the called function, the
caller, their shared assumption, or upstream of both? A null
that crashes in `parse()` may originate in the loader that
should never have produced null.
- **When did it start?** `git log` and `git blame` on the
affected code. For regression-shaped bugs the commit that
broke it often tells you why; even for non-regressions, the
commit messages surrounding the affected lines surface the
original intent and context.
- **Could the same class of bug exist elsewhere?** Grep for
similar patterns — same function called from other sites, same
assumption made elsewhere. If yes, decide whether the fix's
scope widens or whether you file follow-up tickets and add an
explicit non-goal ("fix here only").
4. **Minimum fix.** Write the smallest change that turns the failing
test green. Refuse to fix adjacent issues in the same PR; note
them for follow-up. (Echoes `adversarial-reviewer`'s scope check
— out-of-scope changes are a Blocker until justified or extracted.)
5. **Verify root vs symptom.** Look at the diff and ask: does this
address what step 3 identified, or does it mask the symptom?
Common symptom-only anti-patterns to refuse:
- **Catch-all exception handlers** that swallow the bug instead
of making the failing call not throw.
- **Defensive checks at every call site** when the invariant
should hold upstream. Twelve `if (x == null) return` callers
of a function that should never return null is masking, not
fixing.
- **Retries around flaky code** when the right fix is to make
the code deterministic.
- **Feature flags that disable the broken path** instead of
fixing it. Flags are for staged rollout, not for hiding bugs.
If the failing test from step 2 still passes under a symptom-only
fix, you wrote the wrong test — go back to step 2 and sharpen it.
6. **Regression test stays.** The failing test from step 2 is the
regression test. It lives in the suite; it must pin the real
invariant so it catches this bug if it recurs. Don't delete it
after the fix lands.
7. **Commit body documents the root cause.** Conventional commit
subject (`fix(<scope>): <subject>`) plus a body explaining what
was wrong (the observable bug), why it was wrong (the root cause
from step 3), and why the fix takes the shape it does. The diff
shows *what*; the commit body shows *why*. Future readers care
more about the latter.
8. **Loop back to the tracker (if any).** Comment the PR URL on the
ticket and apply the next transition. The mechanism is
adopter-specific (Jira MCP, Linear CLI, `gh issue comment`, etc.);
the obligation — keeping the ticket synced — is universal.
## Anti-patterns to refuse
- **Fixing forward without a reproduction.** The obvious fix is
wrong about a third of the time, and you can't tell which third
until the test fails red first.
- **Fixing the bug plus adjacent cleanup in one PR.** Each cleanup
is its own PR with its own justification. Bug-fix PRs are for
fixing bugs.
- **Adjusting the spec or the test to match the buggy behavior.**
If the spec and the fix disagree, one of them is wrong — surface
that explicitly before continuing, don't paper over it.
- **Closing as "not reproducible"** without trying hard enough.
Document what was tried, on what version, with what data, before
giving up. "Couldn't reproduce on my machine" is a hypothesis
worth testing, not a closing condition.