Vibe Coding With Guardrails: Ship Fast Without the Incidents
The dirty secret of vibe coding is that the people doing it well are not actually vibing. They look relaxed β prompt, accept, ship, repeat β but underneath the flow there's a quiet system of checks deciding what gets accepted blind and what gets read line by line. The people who copied the vibe without the system are the ones writing incident postmortems.
I've shipped side projects and client work this way for a while now, and as of this writing the models are good enough that the bottleneck has fully moved: the constraint isn't whether the AI can write the code, it's whether you can catch the 5% of output that's confidently wrong before it reaches production. This piece is the guardrail system that catches it β cheap enough that you keep the speed, strict enough that you keep your weekends.
The core loop: generate, gate, merge
Unguarded vibe coding has one step: accept. Guarded vibe coding has three, and the middle one is where all the safety lives.
Generate. Prompt with intent, constraints, and context. Let the model work in reasonably small increments β a feature, not a subsystem. Big-bang generations produce big-bang failures that are miserable to bisect.
Gate. Every generation passes through the same checklist before it's accepted, and the checklist is graduated: cheap automatic checks run on everything, expensive human attention is spent only where it pays. Tests run. Linter runs. The diff gets skimmed β not read, skimmed β for scope: did the model touch files it had no business touching? Then the risk triage: does this diff hit any of the always-read categories below? If yes, the skim becomes a real review.
Merge. Small commits, honest messages, and a working state at every step. When (not if) a generation turns out to be subtly wrong two days later, the difference between "revert one commit" and "untangle one giant commit" is the difference between a five-minute fix and a lost evening.
The loop sounds like ceremony. In practice the gate takes ninety seconds for a routine diff, because the automation carries most of it. You're not slowing down to review everything; you're building the reflex that decides what deserves review β which is the actual skill of this era.
Test-first prompting: encode intent before the model interprets it
Here's the highest-leverage habit in the whole system: write the tests β or at minimum, dictate them β before the generation.
The reasoning is simple. A test written before the code encodes what you mean. A test written by the model after the code encodes what the model did. AI-generated post-hoc tests pass at a suspiciously high rate for the same reason a student grading their own exam does well β they're confirming, not checking.
So the workflow becomes: describe the behavior, have the model write failing tests, read those tests yourself (they're short, and reading tests is far easier than reading implementations), correct any that misread your intent, and only then let it implement until green. You've turned a vague prompt into an executable spec, and every future regeneration of that code has a harness waiting for it.
This pairs with the prompt patterns I've written up separately in 5 Prompt Patterns That Keep AI Code on Rails β test-first is one of five, but it's the one I'd keep if forced to choose.
When you must actually read the code
You cannot read everything. That's not defeatism; it's arithmetic β a productive AI-assisted day produces more code than a human can meaningfully review in a day. The honest move is to admit reviews are a scarce resource and spend them where wrongness is expensive:
- Auth, sessions, and permissions. A subtle bug here isn't a bug, it's a breach. Read every line, every time.
- Money. Payments, billing, credits, anything that rounds. Models love floating-point money right up until the ledger doesn't balance.
- Migrations and anything irreversible. Generated code that drops, renames, or rewrites data gets read twice and rehearsed on a copy first.
- Trust boundaries. Input parsing, file uploads, anything user-controlled that touches a query or a shell.
- Dependency changes. A model adding a package is making a supply-chain decision on your behalf. Check what it added and why.
- Secrets and config. Generated code has a persistent habit of "temporarily" inlining credentials.
Everything outside these categories gets the ninety-second gate and moves on. This split β total paranoia in a narrow zone, calibrated trust everywhere else β is what lets the workflow stay fast. The failure modes that follow when people skip this are predictable enough that I cataloged them in Where Vibe Coding Breaks; auth and migrations lead the league table by a wide margin.
The guardrail budget
| Guardrail | Time cost per change | What it catches |
|---|---|---|
| Tests written before generation | 5β15 min | Wrong behavior, silent regressions, intent drift |
| Lint, types, CI on every diff | ~0 (automated) | Sloppy output, broken builds, dead code |
| 90-second diff skim | 1β2 min | Scope creep, surprise file edits, inlined secrets |
| Full read of high-risk categories | 10β30 min | Breaches, data loss, money bugs |
| Small commits, working state each | ~0 (habit) | Unbisectable failures, painful reverts |
| Staging deploy before prod | 5 min | Config drift, works-on-my-machine surprises |
Total overhead on a typical feature: perhaps twenty minutes. Cost of the incident it prevents: your evening, your data, or your users' trust. I've been on both sides of that trade, and twenty minutes is a bargain.
Guardrails scale with stakes
One size does not fit all, and pretending otherwise kills either your speed or your safety. A sane gradient:
Throwaway prototypes: no guardrails. Genuinely. Vibe freely β the whole point is speed, and the blast radius is zero.
Personal tools with your own data: tests on the core path, skim everything, read anything touching files or credentials.
Anything with users: the full loop above, no exceptions, plus staging.
Anything with users' money or personal data: the full loop, and the high-risk categories get read by a human who's imagining the postmortem.
The mistake I see most is inertia in both directions β people bringing production ceremony to weekend toys, and (much worse) toy habits to production. Decide the tier before you start prompting, because you won't decide it honestly afterward when the diff is green and the demo works.
The uncomfortable opinion
Vibe coding didn't remove the engineering from software; it moved all of it into two places β specification and review β and plenty of people quietly skipped both and called it productivity. The model writes the middle now, and the middle was never where the hard part lived. If your guardrails feel like friction, that feeling is the job. It's the same job it always was, wearing a faster costume.
Ship fast. Read the scary parts. Test before you generate. That's the whole religion.
More workflow teardowns like this land in the newsletter β including the full idea-to-deployed walkthrough this system slots into. Get on the list.
Frequently asked questions
What is vibe coding?
Coding by describing what you want to an AI model and accepting its output with minimal manual editing β steering by intent and iteration rather than writing the code yourself. The term started half-ironic and became a real workflow.
Is vibe coding safe for production applications?
It can be, with guardrails: tests written before generation, a review loop that gates merges, and mandatory human reading of security-sensitive code. Unguarded vibe coding is fine for prototypes and dangerous for anything holding user data.
Do I still need to know how to code to vibe code?
For anything beyond toys, yes. You need enough fluency to read diffs, spot suspicious patterns, and know what to test. The skill shifts from writing code to specifying and auditing it β which is still an engineering skill.
What parts of AI-generated code should I always read?
Auth and session logic, anything touching money, database migrations, input handling at trust boundaries, dependency changes, and any code that manages secrets. These categories cause outsized damage when wrong.
How do tests help when the AI wrote the tests too?
Write or at least specify the tests before generation, so they encode your intent rather than the model's interpretation. AI-written tests after the fact tend to confirm what the code does, not what it should do.