Home Insights Story Studio Work With Me Free Growth Plan
Vibe Coding in Production: Guardrails That Keep AI-Written Code Safe

Vibe Coding in Production: Guardrails That Keep AI-Written Code Safe

AI writes most of my production code. These guardrails keep it safe: lint rules, database triggers, and invariant tests built from real production incidents.

AI writes most of my code. I’ll say that plainly, because a lot of founders doing the same thing pretend otherwise.

Dszape — my hotel management SaaS, 245+ API routes, 719+ source files, 180+ database migrations, built solo — is overwhelmingly AI-written. It handles real bookings, real check-ins, real guest billing in rupees. And it’s safe to run in production not because the AI is careful, but because the system around the AI makes carelessness survivable.

That’s the part of vibe coding nobody talks about. The demos show the speed. Nobody shows you the folio screen displaying ₹0 for weeks because a type cast hid a missing database column.

This post is my honest account of what actually broke, and the guardrail pattern that came out of it: every incident becomes a rule, and every rule becomes three layers of enforcement — a lint rule, a database trigger, and an invariant test. If you’re a founder or operator adopting AI coding, this is the post I wish someone had written for me before my first P0.


The Real Risk of AI-Written Code Isn’t Bad Code

Here’s what surprised me: the AI’s code is rarely wrong in the obvious sense. It compiles. It reads cleanly. The happy path usually works first time.

The risk is that AI-written code fails silently. It’s trained to produce code that looks complete, and code that looks complete often handles errors by quietly absorbing them. A human junior developer who doesn’t know what to do with an error asks you. An AI that doesn’t know what to do with an error writes catch and moves on — and it does this with total confidence, at scale, across hundreds of files.

For a content site, silent failure is an annoyance. For an operations platform where a hotel’s billing runs through your code, silent failure is the worst possible property. A crash gets noticed in minutes. A silent failure gets noticed when a customer does their monthly reconciliation.

Every guardrail I’m about to describe exists because of a specific silent failure that made it to production. These aren’t best practices I read somewhere. They’re scar tissue.

Incident One: The Silent Error That Reset Every User

One day, every signed-in Dszape user got bounced back to onboarding step one. Users with fully set-up hotels, live data, published booking pages — all treated as brand-new accounts.

The cause was three lines that existed, in slightly different copies, in three different files. Each one looked up the current user’s hotel from the database, took the data from the response, and ignored the error that came back alongside it. When an auth configuration change made those queries start failing, the code didn’t crash. The error went nowhere, data came back empty, and the code concluded: this user has no hotel, send them to onboarding.

The query never said “I failed”. It was never asked.

The lesson is bigger than the bug: in AI-written code, silence is not success. Reading the data without checking the error is exactly the kind of pattern an AI reproduces endlessly, because it looks tidy and works in the demo.

The fix, three layers deep:

  • A hard rule: never take the data from a database query without also handling the error.
  • A lint rule that flags the pattern automatically, so no future AI-written code ships with it.
  • A single canonical “look up the current user’s hotel” function that every screen must use — so “the query failed” and “the user is genuinely new” can never be confused again — with tests that prove a signed-in user with existing data sees their data.

Incident Two: as any and the ₹0 Folios

This one ran for weeks before anyone noticed, which tells you how quiet it was.

In TypeScript, as any is an escape hatch that tells the compiler “trust me, stop checking this”. AI coding assistants reach for it constantly, because it makes red squiggles disappear and the code look finished.

In my billing engine, as any casts on database writes were hiding the fact that the columns being written to did not exist. A migration file had been moved without a replacement, so the code was confidently inserting charges and settlements into columns the database didn’t have. The database rejected the writes. The casts and swallowed errors meant nobody — human or AI — ever saw the rejection.

The result: 50 of 56 production folios showed a balance of ₹0. Guest charges, silently vanishing, for weeks. In hotel terms: the billing system was down and smiling.

The fix, three layers deep:

  • A rule: no as any on database insert or update payloads, ever. If the compiler complains about a column, that’s the system telling you the column might not exist. You don’t silence the smoke alarm.
  • An ESLint rule that fails the build on the pattern — which, when first switched on, surfaced over a hundred existing violations. Humbling, and exactly the point.
  • A verification rule: a migration file on disk is not an applied migration. Before code references a column, its existence gets verified against the live database. Financial operations must throw on failure — never return a quiet null.

Incident Three: The Side Door That Created Orphaned Records

The third incident is the sneakiest, because the code that caused it was individually fine.

Dszape has canonical helper functions for critical writes — one function through which all reservations get created, one through which all billing lines get posted. These helpers do the unglamorous work: writing linked records to two tables atomically, applying tax logic, checking that the target isn’t already settled and locked.

Then an AI agent, building a new integration, wrote a perfectly reasonable-looking direct insert into the reservations table. It didn’t know the helper existed, or why it mattered. The insert skipped the dual-write, so the records it created had no link to the rest of the system. Orphaned reservations: real guests, in the database, invisible to the arrivals dashboard, the calendar, and the front desk.

Later, a different route did the same thing to billing — a direct write that skipped the checks and posted a charge into a folio that was already settled, locked, and fully paid. It silently reopened closed books.

The pattern to fear: AI assistants don’t know your architecture’s load-bearing walls. Every convention that lives only in a human’s head is a convention the AI will eventually violate, politely and confidently.

The fix, three layers deep — the full pattern this time:

  • Lint rule: direct inserts to protected tables fail the build everywhere except the one canonical helper file. The side door is bricked up at development time.
  • Database trigger: the database itself rejects illegitimate writes — an unlinked reservation, a charge into a settled folio — no matter who or what wrote them. Even a script, a seed file, or a future AI that’s never seen my lint config hits this wall.
  • Invariant test: automated tests assert the invariant itself (“a settled folio cannot gain new charges”) and, crucially, that the production code actually routes through the helper. I learned from mutation testing my own suite that testing the helper in isolation isn’t enough — you can delete the wiring and stay green.

The Three-Layer Pattern: Lint Rule + Database Trigger + Invariant Test

Notice the shape all three incidents share. That’s the whole system:

  1. Incident happens. Something silent makes it to production.
  2. It becomes a written rule. Specific, with the incident attached, in the project instructions every AI session reads. Not “be careful with errors” — rather “never take data without error, because of the day every user got reset”.
  3. The rule becomes three enforcement layers:
    • Lint rule — catches the mistake at write time, in the editor, before it’s even committed.
    • Database trigger — catches it at runtime, at the last possible moment, regardless of which code path (or which AI) made the write.
    • Invariant test — catches regressions and proves the protection itself still works.

Why three? Because each layer fails differently. Lint can be disabled or bypassed by a script. Tests only cover what someone imagined. The database trigger is the paranoid backstop that doesn’t care about anyone’s intentions. Defence in depth, in ops language: a checklist, a supervisor, and a lock on the door.

The deeper principle for anyone managing AI-written code: never rely on the AI remembering. Sessions end. Context windows fill. A rule that lives in prose is a suggestion; a rule that lives in a lint config and a trigger is a law. My job has shifted from writing code to legislating for the thing that writes it — which, after ten years of writing SOPs and building processes that survive staff turnover, feels very familiar. AI agents are the ultimate high-turnover workforce: brilliant, fast, and starting from zero every morning.

What This Means If You’re Adopting AI Coding

Practical translation for founders and operators, in order of value:

  1. Ban silent failure first. Ask whoever’s building (human or AI): what happens when this database call fails? If the answer involves the word “nothing”, that’s your first P0, pre-booked.
  2. Write your rules from your incidents, not from the internet. Generic best practices bounce off. A rule with your own outage attached gets respected — by you and by the AI reading it.
  3. Put guardrails in tools, not prompts. Lint rules, database constraints, and CI checks are the only instructions an AI can’t forget.
  4. Protect the money paths triple. Anything touching charges, payments, or balances gets all three layers before it ships. Everything else can earn its guardrails as it breaks.
  5. Verify against the live system. Files lie. Migration files especially. If the code references a column or table, confirm it exists in the actual database.

None of this slows me down in any way that matters. The speed of AI coding is real — I’ve written about the full workflow in how I build SaaS solo with AI, and the enforcement itself is largely run by the agent system that reviews and verifies the work. The guardrails are what let me use that speed on software that touches money, instead of only on demos. It’s also a healthy input to the build-vs-buy decision: if you’re not willing to build the guardrails, buy the software.

Vibe coding got a reputation as reckless because people ship the vibe without the system. The vibe writes the code. The system keeps the promises.

FAQ

What is vibe coding?

Loosely: describing what you want in plain language and letting AI write the code, iterating by feel rather than by hand-crafting every line. It’s how most of Dszape was built. The term gets used dismissively, but the approach is fine — what’s reckless is doing it in production without enforcement around it.

Is AI-written code safe for production?

It can be, with guardrails the AI cannot bypass: lint rules that fail the build, database triggers that reject illegitimate writes, and invariant tests that prove the protections hold. What’s unsafe is trusting the AI’s confidence, because AI code fails silently far more often than it fails loudly.

What is the three-layer guardrail pattern?

Every production incident becomes a written rule, and every rule gets enforced three ways: a lint rule (catches the mistake at write time), a database trigger (rejects the bad write at runtime, regardless of source), and an invariant test (proves the rule and its wiring still hold). Each layer covers the others’ blind spots.

Do guardrails slow down AI development?

Barely, and the trade is lopsided. A lint failure costs seconds. My billing incident silently corrupted weeks of production folio data. The guardrails also make the AI faster over time, because every rule carries context that stops it repeating a mistake I’ve already paid for.

I’m not technical — can I still enforce this?

You can enforce the questions, which is most of it. Ask what happens when a write fails. Ask how a rule is enforced when the AI forgets it. Ask whether the database itself would reject a bad write. I come from operations, not engineering — treating this as process design rather than programming is exactly why it works.


I write about building production software solo with AI — including the incidents most people don’t publish. If that’s your kind of thing, there’s more about me and this journey here.

Evan D'Souza
Evan D'Souza
Growth Architect & Startup Consultant

10+ years of hands-on experience helping early-stage startups scale from chaos to traction. Former founding team member at multiple startups in SaaS, D2C, and community-led businesses.