Introduction
I read a lot about AI-assisted development, and one refrain keeps coming up: vibe coding can't work in production. You generate code you never read, stack features you no longer understand, and one day everything breaks on real data with real users.
I get the underlying concern, and the risk described is real. But I think the conclusion people draw from it is wrong. The answer is not to stop generating code. The answer is to build a system that lets you iterate fast and ship to production safely. The difference between those two things is the difference between an opinion and a method.
My Context: Generation Was Not a Luxury
Our internal platform was built evenings and weekends, alongside a full-time job. Without code generation, it simply would not exist: the available time would not have allowed it. The question "should we let AI write code" was never a theoretical debate for us. The real question was always: what lets me sleep at night when this code is running in production on company data?
The answer is not "review every line." At this volume, exhaustive code review is an illusion of control: you skim, you approve, you reassure yourself. The answer is the system around the code. Here is ours, as it runs today, piece by piece.
One Single Path to Production
At our company, every change goes through a pull request, no exceptions. Nobody pushes directly to production, not me, not the AI. And this is not a team discipline that holds as long as people are paying attention: it is a hard lock on the GitHub side, where branch protection makes merging impossible without a pull request and a green CI. That pull request can only be merged if the entire continuous integration pipeline is green: linting, syntax checks, more than 600 automated tests, a dependency security audit, a full inventory of everything that makes up the application, static security analysis of the code, and smoke tests that actually start the application and click through the interface the way a real user would.
The core point is this: the system does not trust the author, whoever that is. AI-generated code goes through exactly the same checks as code I would write by hand. Once the pull request is merged, deployment is automatic. And every pull request gets its own preview environment: a throwaway copy of the application where you click around and verify before production ever sees anything.
A note on environments. The classic chain is development, staging, production. At our size, we deliberately keep it simpler: two levels are enough, the local dev environment and production, with per-pull-request previews as the verification gate between the two. A permanent shared staging environment would carry a real maintenance cost for marginal benefit at 11 people, and it would inevitably drift from production. What matters is not the number of environments, it is the rule they enforce: no change reaches production without having run somewhere else first.
Every Incident Becomes a Lock
Two simple rules carry most of the reliability.
The first: every feature ships with its test, in the same delivery. No "we'll test it later." Business logic lives in pure functions, tested without any database dependency, so they are fast and impossible to skip.
The second: every bug fix becomes a regression guard. The test describes the incident, the date, the cause, and verifies the fix is still in place. We have close to 240 of these today, and they are our real memory: the system can only get harder to break, like a ratchet that never clicks back down. An incident that happened once can never return silently.
We pushed this ratchet logic further: some CI checks measure a debt (for example, legacy inline styles in the interface that are incompatible with a strict security policy) and reject any pull request that increases it. The existing backlog gets cleared project by project, but the counter is only allowed to go down.
The System Constrains the AI Too
This may be the least obvious part, and the most important: the project rules live in the repository, not in my head.
A context file, versioned with the code, describes the project invariants: what must never be done, known pitfalls, settled decisions. The AI assistant reads it at the start of every session. Repeatable procedures (how to ship a change, the end-of-development checklist, how to audit a given subsystem) are also versioned, like recipes that each session reads rather than reinvents. The result: whether it is me on a Monday evening or an AI session on a Saturday morning, the work starts from the same rules, and lessons learned survive from one session to the next.
We even have an explicit definition of done, in the repository: a feature is not finished until it has its test, its updated documentation in the same pull request, its full lifecycle covered (creating implies editing and deleting), and its access decision (who is allowed to use it, and how you open it to others without rebuilding from scratch). These were questions we used to ask after the fact; now they block the delivery.
One last guardrail, more human in nature: beyond an estimated two days of work, no project starts without a five-line mini-scoping document validated upfront (the problem, the time budget, the approach, what we will not do, the risks). Five lines are enough to avoid scope creep, and the AI generates far better output when the scope is clear.
Even Documentation Is Constrained
One detail I am particularly happy with: every documentation page has a word budget, enforced by the CI pipeline. If a page goes over, the rule is not to raise the budget, it is to compress: trim what is outdated, merge, summarize. Forgetting is a feature. Documentation that grows without limit becomes inaccurate, and inaccurate documentation is worse than no documentation at all, especially when an AI is reading it at the start of every session to understand the project.
After Deployment: Detect Fast
Everything above acts before production. But an honest system assumes it will let things through, and gives itself the means to see them in minutes rather than weeks. That is the second half of the setup.
- Healthchecks at deployment. The hosting platform verifies the application is responding before switching traffic: a deployment that does not start never replaces the one that was running.
- Operational alerts in our Discord. When an incoming data feed starts rejecting records or a scheduled job falls behind, the team sees it in the ops channel in real time, not in a monthly report.
- A nightly integrity watchdog. Every night, a check scans the database for inconsistencies: orphaned references, counters that have drifted. Data problems surface before a human runs into them.
- A daily evaluation of the embedded AI. Our internal assistant runs a battery of calibrated questions every morning, including access-rights probes: if a response exposes data to someone who should not see it, that is an immediate red alert.
- Product observability with PostHog. Session replay with systematic masking (never a typed string or a number on screen), browser error tracking, and a handful of product events with locked names. We see what users actually experience, not what we imagine.
The overall logic: pre-merge checks prevent, post-deployment observability detects. One without the other is incomplete; together, they leave very few places for an error to hide.
The Real Debate Is Not the One People Think It Is
What critics of vibe coding are actually describing is not an AI problem. It is a problem as old as the craft itself: unverified code in production. A tired, rushed human developer who ships without tests or review produces exactly the same disasters. AI did not create this risk, it made it massive and visible, because it democratized code production.
So the relevant dividing line is not "human code versus generated code." It is "verified code versus unverified code." And on that dividing line, my conviction is simple: a small team equipped with a real verification system will iterate faster, and more safely, than a large team that reviews everything by hand.
Where to Start
If you are generating code today and production makes you nervous, do not start by reading more. Start by locking things down more:
1. Block the direct path to production, even for yourself.
2. Set up a CI pipeline that fails on tests and linting, even a minimal one to start.
3. Enforce the rule: one feature, one test, same delivery.
4. Turn every bug into a regression test, systematically.
5. Version your rules and procedures in the repository, so the AI works under the same constraints you do.
6. Instrument production: errors, replays, alerts. What CI lets through must be visible in minutes, not weeks.
7. Add the rest, ratchet by ratchet, after every near-miss.
The question to ask is not "can I trust the AI." It is: what in my system catches an error before my users do? As long as the answer is "me, reviewing the code," you have a problem, with or without AI. The day the answer is "my system, on every delivery," the vibe coding debate becomes what it always should have been: a question of tooling, not a question of faith.






