MODULE 3 · EVERYDAY WORKFLOWS / 25 MIN Free NOVEMBER 2026 EDITION · CLAUDE CODE 2.1.318
Fixing bugs test-first
Reproduce a bug with a failing test, let Claude fix it until the test passes, and guard against fake fixes.
Updated 5 November 2026 · Reviewed 5 November 2026
What you’ll be able to do
Fix bugs in a way that is proven (a test shows the bug is gone) and durable (the test stops it from quietly coming back), and recognise when an AI “fix” only hides the problem.
Why this matters
“Fix this bug” is one of the most common requests to an AI coding tool, and one of the easiest to get subtly wrong. Without a precise definition of the bug, Claude might fix a different problem, fix only the exact example you mentioned, or make the symptom disappear without addressing the cause. And without a test, the same bug can reappear months later, after an unrelated change.
The solution is an old engineering habit that works especially well with Claude: reproduce the bug as a failing test first, then fix it. The failing test is an exact, executable description of the bug. Claude can run it as many times as it needs, and when it passes, you both know the bug is gone.
The test-first bug-fixing loop
- Reproduce: write a test that fails because of the bug.
- Commit the failing test. It becomes your anchor: proof the bug existed, and a fixed point to compare against.
- Fix: Claude changes the implementation until the test passes, without changing the test.
- Check for fake fixes and for anything else the change broke (regressions).
- Commit the fix.
Exercise: plant and fix a bug
You’ll practise on the loop-practice project from lesson 1.2 by planting a bug yourself, so you know exactly what “fixed” looks like.
1. Start from a clean, committed state on a new branch. If you haven’t committed your earlier work yet, commit it now, so later diffs only show this lesson’s changes:
!git add -A
!git commit -m "Lesson 1-2 helpers"
!git checkout -b fix/slugify-numbers
2. Ask Claude to reproduce the bug as a failing test:
Write a failing test in slugify.test.js for this bug report:
"slugify('Top 10 Tips') returns 'top-tips' instead of 'top-10-tips'".
If the current implementation already handles it, first change slugify.js to
reproduce the bug (strip digits). Run the tests to show the new test fails.
Don't fix it yet.
What you’ll see: Claude adds the test, changes slugify.js so it strips digits (planting the bug), and runs the tests. The new test fails with a message along the lines of expected “top-10-tips”, got “top-tips”. A failing test is the goal of this step.
3. Commit the failing state:
!git add -A
!git commit -m "test: reproduce digits being stripped in slugify"
4. Ask for the fix:
Fix slugify so the new test passes. Do not modify any tests. Run the full test
suite and show me the diff when done.
What you’ll see: Claude changes only slugify.js, runs the whole test suite, and shows the diff. All tests pass.
Notice the three instructions in that prompt: the goal (make the test pass), a constraint (don’t touch the tests), and done means (run the full suite). These are the four parts from lesson 2.1 at work.
Guard against fake fixes
Sometimes a test passes for the wrong reason. Watch for these three, and check every bug fix against them:
| Fake fix | What it looks like | Why it’s wrong |
|---|---|---|
| Changing the test | The test’s expected value now matches the buggy output | The bug is still there; the test was made to agree with it |
| Special-casing the input | Code like if (text === 'Top 10 Tips') return 'top-10-tips' | Only the example works; every similar input is still broken |
| Only the new test passes | The new test is green, but another test now fails | The fix broke something else |
Check the diff yourself:
!git diff HEAD --stat
What you’ll see: the list of files changed since your commit. For a real fix, only slugify.js should appear. If a test file is listed, ask Claude why.
Then make the rule permanent, so you never have to repeat it. Add this line to your project’s CLAUDE.md (lesson 2.2):
- When fixing a bug, never modify existing tests to make them pass; fix the implementation.
5. Commit the fix, with a message that says what was wrong.
Real-world bugs: give Claude the evidence
For bugs in your own projects, the quality of the fix depends on the quality of the evidence. Paste the exact error and stack trace, plus how to trigger it if you know:
This is failing in production:
TypeError: Cannot read properties of undefined (reading 'id')
at getOrderSummary (src/services/orders.ts:88:31)
...
It happens for orders that have no customer attached. Reproduce it with a
failing test first, then fix the root cause, not just the symptom. Explain
the root cause in one paragraph.
The phrase “fix the root cause, not just the symptom” matters. Without it, a common “fix” for the error above is to add a check that silently skips the missing value. The crash stops, but the real question, why the value is missing, goes unanswered.
Common mistakes
- Fixing before reproducing. Without a failing test, you can’t prove the fix works or catch the bug returning.
- Only running the new test. Always run the full suite to catch regressions.
- Accepting a changed test. Unless the test itself was wrong, a bug fix should only change the implementation.
- Paraphrasing the error. Paste the exact text; line numbers and wording are evidence.
Verify it worked
- You have a commit with a failing test, followed by a commit that fixes it
- The fix commit doesn’t change any test files
- The full test suite passes
- Your
CLAUDE.mdhas a rule against fake fixes
Knowledge check
3 QUESTIONS · PASS MARK 80%Tip: select the text that's wrong before clicking, and it's quoted in your report.