← All posts
Best practices

Every production error should die exactly once

Look at the action items on your last three postmortems. There's a good chance each one ends with the same line: "Add a regression test." Now go check whether those tests exist. If your team is like most, at least one of them doesn't. The fire got put out, the incident channel went quiet, and the test that was supposed to keep the bug dead never got written. Then in Q4 the same bug comes back, and someone says "wait, didn't we fix this?"

The bug itself was never the real problem here; bugs happen, and a team that ships anything ships them constantly. What went wrong is that the incident produced nothing durable. The fix lived in one engineer's head and a closed PR, and the day that engineer switched teams the system quietly forgot the whole episode had ever happened, which is why the same bug can walk back into checkout in Q4 and blindside a room full of people who genuinely believed it was dead.

Friction is what keeps the test unwritten, not laziness. Writing a good end-to-end test at the tail end of an incident means reproducing the exact failing state, wiring up selectors, and asserting on the right outcome, an hour of fiddly work that lands right when everyone is exhausted and wants the ticket closed. The intention is real every single time. The activation energy is just too high, so the loop stays open.

The fix is to lower that activation energy to near zero. Here's the loop that actually closes.

1. The error captures itself, with context

You can't write a regression test for a failure you never saw. Most production JavaScript errors are invisible to the team that shipped them: they happen in a user's browser, throw in the console, and die there. The user rage-clicks, gives up, and never files a ticket.

So the first move is to capture the error where it actually happens, in production, in the real browser, with the context you need to reproduce it. A stack trace on its own won't do. You need the URL and the grouped exception, enough to stand the failure back up on your own machine, and that is exactly what production telemetry is for: the difference between knowing something's wrong and knowing precisely what broke and where. Pair that error stream with web-vitals history and you also catch the slow-burn regressions, like the deploy that quietly doubled load time, not just the loud exceptions.

The point is that the incident starts from a real, captured signal, not from a secondhand bug report three days later.

2. Reproduce it from what was captured, not from a guess

Now you reproduce. The trap here is debugging from imagination: reading a stack trace and guessing at the sequence of clicks that produced it. Guessing is slow and often wrong, and a regression test built on a wrong reproduction protects against a bug that never existed.

A production error doesn't hand you a recorded session. It hands you a URL and a grouped stack trace, which is still enough to work from directly instead of freehand. Follow the same route, hit the same edge case, and confirm you're standing up the same exception TestVibe grouped, not a lookalike that happens to throw from the same function.

By the end of this step you have a precise, confirmed answer to the only question that matters for a regression test: what sequence of user actions produces this failure?

3. Describe the failing behavior in plain language

This is the step that normally costs an hour, and it's the one that collapses to a sentence.

You already know the reproduction. Instead of hand-writing the Playwright (importing fixtures, picking resilient locators, wiring the assertion), you describe the behavior the bug violated:

Given a user with an empty cart
When they apply a promo code and then remove the last item
Then the cart total shows $0.00 and no negative price is displayed

That's the regression: the negative-total bug that just took down checkout, written as the behavior that should hold instead. From that description, TestVibe generates the actual Playwright test, runs it against your live app in an isolated sandbox, and only marks it done once it genuinely passes. You're not trusting generated code on faith, because the test had to execute green against the real application before it joined the suite. (Worth being honest about what that does and doesn't buy you: execution proves the test runs; a human still has to confirm the spec describes the right behavior.)

The hour of fiddly work is gone. What's left is the one thing a human should actually own, which is stating what ought to be true in the first place.

4. It runs forever, on autopilot

A regression test that runs once is a fossil. The whole value is that it runs after you've forgotten the incident, on the exact change that would reintroduce the bug.

So the last step is to make sure it never depends on anyone remembering to run it. Scheduled automations re-run the suite on a cadence, nightly or chained to fire the moment another run finishes, and they notify you only when something actually regresses, not on every green run. The test you generated at 2 a.m. during the incident is now a standing sentinel. When someone six months from now refactors the cart and reintroduces the negative-total math, the test goes red before it reaches a user, with a fresh Playwright trace attached so triage starts from evidence, not archaeology.

That's the loop, closed:

Production error captured → reproduced from what was captured → described in plain language → generated and verified → re-run forever.

Why this changes the economics

"Add a regression test" rots on postmortems because of a cost mismatch: the benefit is diffuse and lives somewhere in the future, while the cost is concrete and lands right now. Every step above goes after that now-cost. The error captures itself instead of waiting for a report, the captured context hands you the reproduction instead of making you guess at it, the plain-language description swaps an hour of Playwright authoring for a single sentence, and the automations take remembering out of the equation entirely.

When closing the loop costs a sentence instead of an hour, engineers actually do it. Do it consistently and the character of your incidents changes: outages stop being folklore traded in Slack threads and become permanent, executable checks that a machine reruns whether or not anyone recalls the original fire.

Take the negative-total bug from step 3. Fix it once, describe it once, and six months later a sandbox somewhere reruns that Gherkin at 3 a.m., catches the refactor that would have brought it back, and goes red before a single customer opens a cart that owes them money.


Want to see the loop end to end — telemetry capture, plain-language generation, scheduled runs with a trace on every failure? Get early access, or read how generation works.

Early access

Ready for tests that write themselves?