CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-04-24Advanced

Write the Repro Test Before Delegating Bug Fixes to Claude Code

After watching Claude Code spin its wheels on ambiguous bug reports, I started writing the failing test myself before delegating. This post walks through the design principles, a concrete repro test, and the three-stage workflow I run in production.

Claude Code197TDD4bug fixingtest designworkflow37

You have probably done this. A bug report comes in, you paste the stack trace and logs into Claude Code, and ask it to fix the symptom. Claude reads the repo, forms a hypothesis, writes a plausible patch, confirms the existing tests pass, and reports back. You try it on a real device, and the symptom reappears. You paste more logs, Claude tries again, and an hour quietly disappears.

The cause of this spin is not Claude Code's capability. It is that I was delegating both "reproducing the symptom" and "fixing it" at the same time, without giving Claude a precise, code-level definition of what "failing" means. This post describes a workflow I now use every time: write a failing reproduction test myself, commit it, and only then hand the fix over to Claude Code. I will share the principles I apply, a concrete test example, and the prompts I use at each stage.

Why a Repro Test First Stops the Spin

When I tell Claude Code "login redirects are broken," Claude starts by guessing how to reproduce the symptom. It reads the test suite, opens adjacent files, forms a hypothesis, and writes a patch. Existing tests stay green, so Claude reports the job done. But Claude never had a way to observe the original failure, so it cannot confirm the fix truly addresses it.

When I hand Claude a test that is guaranteed to fail under the bug conditions, two things become unambiguous:

  1. The goal of the fix is to turn this specific test green.
  2. Correctness is measured by this test turning green plus no other test going red.

That is a contract written as code, not as a vague symptom description. Claude can focus on satisfying it.

There is a second, quieter benefit. Writing the repro test forces me to define the symptom precisely. "Login redirect is broken" could mean a 200 response with no Location header, a 302 with the wrong URL, or a client-side navigation that silently stops. I cannot write a test without picking one. That precision then flows into the prompt I hand to Claude, and the whole loop gets tighter.

Four Principles for a Minimal Repro Test

The shorter and more precise the repro test, the less Claude wanders. These four principles are what I have settled on.

Principle 1: Write it inside the existing test suite

Do not write it in an isolated sandbox file. You lose access to the project's fixtures and helpers, and Claude does not get to see the test sitting next to its peers. I put bug repros under tests/bugs/ and name the file after the ticket ID. That gives future me a very obvious way to find "the test I wrote when ticket 234 came in."

Principle 2: One observable fact per test case

Do not combine "login succeeds + redirect is correct + session cookie is set" into one test. When it fails, you cannot tell which assertion broke. One assertion per test keeps the failure signal sharp. Claude reads it and heads straight for the one condition it needs to satisfy.

Principle 3: Test at the boundary where the bug lives

If the bug manifests at the HTTP, DB, queue, or external API boundary, write the test at that boundary. In FastAPI projects I reach for TestClient and hit the real endpoint, rather than mocking out the framework. Mocks introduce slack, and slack is exactly where bugs hide.

Principle 4: Pin the repro to one variable pair

If the bug happens for "specific user role × specific request order," parameterize both and add cases. Showing Claude a 2×2 table of "green vs. red" cases makes the source of divergence far easier to localize.

A Concrete Example: Redirect Failure for Stale Sessions

Here is a simplified version of a real bug I hit on a small FastAPI auth endpoint: "users with old sessions get 200 back from login instead of 302."

When I was still spinning, this was my prompt:

Login is not returning 302. Please look at src/auth/login.py
and related tests and fix it.

Claude would patch a nearby conditional, confirm existing tests stayed green, and declare done. The bug would still reproduce on real devices.

Here is the repro test I wrote before re-delegating:

# tests/bugs/test_login_stale_session_redirect.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
from tests.factories import UserFactory, make_stale_session
 
client = TestClient(app)
 
@pytest.mark.parametrize(
    "session_age_days, expected_status",
    [
        (0, 302),   # fresh session: redirect as normal
        (8, 302),   # stale session: should still redirect (currently returns 200)
    ],
)
def test_login_redirects_regardless_of_session_age(session_age_days, expected_status):
    user = UserFactory.create(password="valid-password")
    make_stale_session(user, age_days=session_age_days)
 
    response = client.post(
        "/auth/login",
        data={"email": user.email, "password": "valid-password"},
        follow_redirects=False,
    )
 
    assert response.status_code == expected_status

The parametrization pins the divergence: fresh sessions pass, stale ones fail. The prompt I send to Claude shrinks to this:

tests/bugs/test_login_stale_session_redirect.py is currently failing
on the session_age_days=8 case (returns 200 instead of 302).

Make that case pass without breaking any other existing test.

Claude is now aiming at a target defined by code, not by prose. And "without breaking any other existing test" kills a whole class of sloppy fixes that touch unrelated behavior.

The Three-Stage Workflow

Once I can write the repro test, I run a three-stage loop with Claude Code.

Stage 1: Write the repro test and commit it

I commit the failing test first, by itself. The commit message makes the intent unambiguous: test(bugs): reproduce stale session redirect issue (#234). CI shows the test failing as expected. Six months later, whoever blames this commit can read its purpose instantly.

Stage 2: Delegate the fix to Claude Code

I keep the prompt tight. The repro path, the tests that must stay green, and the search surface for the fix, all in one message.

Repro test: tests/bugs/test_login_stale_session_redirect.py
Must stay green: tests/auth/, tests/session/
Likely fix locations: src/auth/, src/session/

Turn the repro test green. Breaking any existing test is a failure.
Before writing code, state your fix plan in under three lines.

The "state your plan in under three lines" constraint is load-bearing. It lets me veto an obviously wrong hypothesis before Claude spends tokens implementing it.

Stage 3: Review the diff — not just against the test, but against intent

When CI goes green, I review on two axes: "is the repro test actually green" and "is the fix aiming at the real cause." The second matters because Claude will sometimes find the cheapest change that makes the test pass, not the change that fixes the bug. In this example, Claude could simply clear the stale session before login. That turns the repro test green. But it also hides the real issue.

If that happens, I push back: "The current patch turns the repro green but does not address the root cause because X. Please try again in direction Y." The re-delegation is cheap now, because the target is still defined in code. Only the direction changes.

Bugs You Cannot Easily Reproduce

Honestly, some bugs resist this approach. The usual offenders:

  1. Concurrency bugs that reproduce once in 1000 runs under load
  2. UI bugs tied to a specific browser build × OS combination
  3. State transitions that only show up after restart or long uptime

Writing a stable test for these is hard, and flaky tests poison the suite. For these I drop down a level: I write a scripted repro, not a test. Something like scripts/repro/<issue>.py that, when run N times, will hit the bug a few times. The prompt becomes: "this script currently fails some fraction of the time. Fix the underlying issue so it stops failing. A proper CI-grade test can come separately." Even stepping down from "deterministic test" to "repeatable experiment" dramatically tightens the delegation.

What Changed After Running This for a While

Three things shifted on my side.

First, my reflex on reading a new bug report became "let me try to write the failing test." Before, I would read code, form hypotheses, and an hour would vanish. Now I spend the first twenty minutes trying to capture the bug in a test. If I cannot, that is a signal I do not yet understand the symptom.

Second, my decision speed on Claude's patches improved more than the patch quality did. The two guardrails — repro test green, existing tests green — remove ambiguity about whether to merge.

Third, the test suite grows. Every bug leaves a regression test behind. After six months you find that every bug you ever fixed is still being checked on every CI run. For a solo developer maintaining a project for years, that compounding value is substantial.

What to Try Tomorrow

If this sounds worth testing, the smallest possible first step is this: on the next bug ticket, commit one failing test file before you delegate anything to Claude Code. Observe how different the delegation feels. If you cannot write the test, note that down — the kinds of bugs that resist tests will tell you where your own leverage is thin. Claude Code is a capable collaborator, but pointing at the right target is, for now, still our job.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-06-24
Working Between Claude Design and Claude Code — Splitting Diverge and Converge
Rough design in Claude Design, fine detail in Claude Code. Now that the two live close together in the desktop app, here is how to run them as a divide-of-labor between diverging and converging — bridges like Link Local code and Send to included.
Claude Code2026-06-20
Handing Claude Design Mockups Straight to Claude Code: Folding the Design-to-Code Loop
The June 17 Claude Design update added codebase-aware generation. Here's how to make your tokens the single source of truth, hand mockups to Claude Code, and collapse the design-to-code round trip — with code you can copy.
Claude Code2026-04-26
Test-Driven Development with Claude Code — A TDD Workflow for the Agent Era
Adding the constraint of 'write tests first' to Claude Code stops the agent's drift and dramatically changes the quality of generated code. Here's the TDD workflow I arrived at after six months of trial and error.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →