← Back to the index
ENG-11 · SEC. 01 Engineering & Coding
No Fix Without a Failing Test
Refuse any bug fix until a test reproduces the bug, so the fix is proven, not vibes.
- FORMAT
- goal
- DIFFICULTY
- intermediate
- TIME
- 15 min
- TOOLS
- claude-code · cursor · codex-cli · gemini-cli
- MODELS
- any
- COPIES
- 0 so far
When to use this
You have a real bug report and the temptation is to let the agent jump straight to a plausible-looking patch. Use this when the bug matters enough that "seems fixed" isn't good enough: anything involving money, data integrity, or a bug that already came back once.
The pattern
Pastes as plain text
There's a bug. Your goal is a fix proven by a test that failed before the fix and passes after it. Changing product code before you can reproduce the bug is not allowed. 1. Reproduce first: write the smallest test that captures the buggy behavior, using this project's existing test framework and style. Run it and paste a redacted verification record. If you can't make it fail, stop implementation: you have not reproduced the bug, and patching now would be guessing. You may continue safe diagnosis, but do not change product code until a deterministic test fails for the reported reason. If a deterministic test is not practical, report that blocker and the manual evidence needed. 2. Only then fix the code. Make the smallest change that turns the test green. 3. Run the new test (must pass) and the surrounding suite (must show no regressions). Paste both outputs. 4. Keep the test in the suite, named for the behavior it protects (e.g. "does not double-charge when a retry fires after success"), not "fixes bug 123". Done means: one new test that fails on the old code and passes on the new code, suite green, both shown in real output. Anything less is not done. Before you do anything, ask me in one message and wait: (1) the bug and how I see it, (2) how to run the tests, if it's not obvious. If I skip (2), find the test command yourself.
Real example output
> The bug: CSV export drops the last row when the table has exactly one full page of results. 1. Repro test added to exportOrders.test.ts: "includes the final row when row count equals page size". Ran against current code: FAIL: expected 25 rows in CSV, got 24. Root cause visible in the failure: the export loop flushes a batch only when hasNextPage is true, so the final batch never flushes when totalRows % pageSize === 0. 2. Fix: flush the remaining batch after the loop in lib/export/csv.ts. 3 lines. 3. New test: PASS. Full export suite: 14/14 passing, no regressions. 4. Test kept as "includes the final row when row count equals page size". It now guards every exact-page-boundary case, not just 25.
Why it works
A patch without a reproduction proves nothing: the bug may live elsewhere, or the code path may never even run. Forcing fail-then-pass pins the exact behavior, catches wrong-root-cause fixes on the spot, and leaves a permanent regression tripwire, so the same bug can't quietly return in six months.
Related patterns
DBG-15Shrink the Repro Until It ConfessesCut a failing case down until every remaining line is load-bearing and the bug has nowhere left to hide.DBG-03Verify Before Claiming FixedDon't accept 'fixed it' from an AI agent until it re-runs the exact failing case and shows real output.DBG-01Root Cause Debugging LoopForce one hypothesis at a time against real evidence, so the AI stops guess-and-check fixing.