Teaching Your AI Coding Agent to Reproduce Bugs Before It Fixes Them
Learn how to force an LLM coding assistant to generate failing reproductions before patches, with prompt contracts, hooks, and practical lessons for front‑end engineers.

Why a Reproduction Step Is Non‑Negotiable
When you hand a bug report to Claude, Copilot, or any LLM, the model will happily generate a diff that looks correct. The problem is that the model never actually runs the code; it just predicts what might work. In my own workflow that meant roughly one‑third of PRs re‑introduced the same issue under a different stack trace.
For a front‑end engineer this is a deal‑breaker because UI bugs are often flaky, environment‑specific, or tied to a particular state transition. If the AI never witnesses the failure, its patch is a guess.
Enforcing a "no fix without failing test" contract
The simplest guard is a prompt contract. At the top of every interaction I prepend:
You are an AI coding assistant. Before you propose any change, you must produce a failing test or repro script that demonstrates the bug. Only after the test fails should you suggest a fix.This tiny instruction changes the model's internal chain of thought. It now asks itself: "Do I have evidence of the bug?" If the answer is no, it generates a minimal repro.
Automation: pre‑commit hook and repro template
Human discipline fades, so I wrapped the contract in a git hook. The hook scans the staged diff for a test/ file that fails when run with npm test. If none is found, the commit aborts.
#!/usr/bin/env bash
npm test --silent || { echo "❌ Reproduction test failed or missing"; exit 1; }The hook expects a file named repro.test.tsx that follows a tiny template:
import { render, screen } from '@testing-library/react';
import MyComponent from '../src/MyComponent';
test('repro: bug X appears when prop Y is true', () => {
render(<MyComponent propY={true} />);
expect(screen.getByText(/unexpected/i)).toBeInTheDocument(); // should fail
});When the AI writes the test, you can run it locally; a red bar tells you the bug is real.
Five hard‑earned lessons
- Rule #1: Never trust a patch without a failing test. The test becomes the contract between you and the model.
- Rule #2: Keep repros tiny. The AI tends to over‑engineer; a three‑line render is enough to surface most UI regressions.
- Rule #3: Version your prompt contract. As you add new edge cases, bump the version string in the prompt so the model knows which rules apply.
- Rule #4: Use deterministic seeds. Pass
--seed 42to the LLM CLI so you can replay a failed run and see exactly what the model thought. - Rule #5: Review the test, not just the diff. The test often reveals assumptions you missed, like missing context providers or async timing.
Putting It All Together in a Next.js Project
In a typical Next.js codebase you can drop the hook into .git/hooks/pre-commit, add the template to test/repro.test.tsx, and update your AI CLI wrapper:
#!/usr/bin/env bash
# wrapper.sh
PROMPT=$(cat contract.txt)
ai-cli ask "$PROMPT" "$@"
# after AI writes files, run the hook automatically
./.git/hooks/pre-commitNow every time you ask the AI to fix a bug, the CI pipeline will fail fast if the reproduction is missing or passes. The result? Your PRs are smaller, your bug queue shrinks, and you spend less time chasing phantom regressions.
What This Means for Your Front‑End Workflow
Adopting a reproduction‑first mindset forces the LLM to act like a junior dev who first writes a failing test. The payoff is immediate: fewer broken UI patches, clearer code reviews, and a reproducible artifact you can hand off to QA. The overhead is a few extra lines of test code and a hook, but those cost seconds compared to a week of chasing a ghost bug.
So next time you spin up an AI coding agent, make the failing test the first deliverable. It’s the cheapest guard you can put around a model that otherwise loves to guess.