← All posts

Vibe Coding Looked Like the Future — Until We Reviewed the Pull Requests

May 15, 2026Faisal KCAI · Engineering

A few months ago, one of our developers submitted a pull request for a new feature on a Flutter app we were building. The PR was big — around 800 lines — and it landed in under two hours. The code compiled, the tests passed, and the feature worked in the staging environment. On the surface, it looked like a great day.

Then Arjun, our senior mobile lead, sat down to review it. Thirty minutes later, he pinged me: “This code works, but I don't think anyone on the team actually wrote it.” He was right. The developer had used an AI assistant to generate the entire feature — prompting, accepting, prompting again — without deeply reading the output. The approach has a name now: vibe coding.

The Allure Is Real

I get it. I've done it myself. You describe what you want, the AI generates it, you test it, it works, you move on. When you're under deadline pressure — and at MVP Apps we're always shipping something, whether it's AWQAF UAE, the National Payment of Zakat platform, or a new module for Emirates Red Crescent — the temptation to let the AI drive is enormous.

The speed gains are not an illusion. A feature that used to take a day can genuinely be scaffolded in an hour. AI assistants are extraordinary at generating boilerplate, wiring up API calls, building CRUD interfaces, and handling the repetitive patterns that make up a lot of day-to-day engineering work.

But scaffolding a feature and shipping a feature are not the same thing. That distinction is where most vibe coding conversations go wrong.

What We Found in the Pull Requests

After that first incident, I asked the team to flag any PR where AI had done the heavy lifting so we could study the patterns. Over two weeks, we collected about a dozen. Here's what we kept seeing.

The code was correct but fragile. AI-generated code tends to handle the happy path perfectly and ignore edge cases. In one NestJS service, the AI wrote a payment validation endpoint that worked for valid requests but returned a raw 500 error on malformed input instead of a structured error response. For a government platform processing real transactions, that's not acceptable.

Duplicated logic everywhere. The AI doesn't know your codebase the way your team does. It generated utility functions that already existed in our shared libraries. In one Flutter module, we found three different date-formatting functions — two generated by AI, one already in our utils package. Nadia, our frontend lead, caught it during review, but if it had shipped, we'd be maintaining three versions of the same logic.

Security assumptions that scared me. One AI-generated authentication flow stored a refresh token in local storage without encryption. Another hardcoded an API base URL instead of pulling it from environment configuration. These aren't exotic vulnerabilities — they're the kind of mistakes a junior developer makes, and they're exactly what AI produces when it doesn't have security context.

Tests that tested nothing. The AI wrote tests that passed but didn't actually verify meaningful behavior. One test suite checked that a function returned a non-null value — but never checked whether the value was correct. Green CI, false confidence.

The Mental Model That Works

After those two weeks, I sat the team down and we talked about it. Not to ban AI tools — we use them heavily and they make us faster. But to set boundaries.

The mental model we landed on: AI is a fast junior developer. It writes quickly, it knows the syntax, it can scaffold anything. But it doesn't understand your architecture decisions. It doesn't know why you chose that particular state management approach. It doesn't know that the payment module has a specific error contract that the mobile app depends on.

You wouldn't let a junior developer merge 800 lines without review. You shouldn't let AI-generated code merge without review either.

The Guardrails We Put in Place

Here's what actually changed in our workflow:

Every PR requires a human review, regardless of how it was written. This was already our policy, but we made it explicit that AI-generated code gets the same scrutiny as human-written code — not less. Rashid and Arjun lead most reviews, and they've gotten good at spotting AI patterns: overly verbose variable names, unnecessary abstractions, boilerplate that doesn't match our project conventions.

No AI-generated code in security-critical paths without senior review. Authentication, payment processing, data encryption, UAE Pass integration — these areas are off-limits for unreviewed AI generation. A senior engineer must read and approve every line. No exceptions, no matter how tight the deadline.

Test coverage gates in CI. We added branch coverage thresholds to our pipeline. If a PR doesn't meet the minimum coverage for the module it touches, it doesn't merge. This caught the “tests that test nothing” problem because meaningless tests don't actually cover branches.

AI-generated code must pass our linter and static analysis. We tightened our ESLint config for NestJS projects and our Dart analysis options for Flutter. The AI generates code that compiles, but it often doesn't follow the specific conventions we've established. The linter catches the drift before it reaches review.

Where Vibe Coding Actually Works

This isn't a “vibe coding is bad” post. Used correctly, it's genuinely powerful.

Prototyping. When we're exploring a new feature idea or building a proof of concept for a client pitch, vibe coding is perfect. Speed matters, perfection doesn't. We recently prototyped a dashboard for one of our concept projects — Pulse Leap — entirely through AI generation. It took an afternoon instead of a week. The prototype served its purpose and was thrown away.

Internal tools. Scripts, data migrations, one-off utilities — the kind of code that runs once or serves a small internal audience. The risk profile is different. If an internal CSV parser has a minor edge case bug, someone notices and fixes it. It's not processing government transactions.

Learning. For developers who are picking up a new stack, AI-generated code is an incredible learning tool. Fahad, one of our mid-level developers, used AI generation extensively while learning React from our Flutter codebase. He didn't ship the AI code directly — he used it to understand patterns and then rewrote it himself. That's the ideal use case.

The Conversation the Industry Needs

The vibe coding debate often gets framed as a binary: either you embrace AI-generated code or you're a luddite. That framing is wrong. The real question is about supervision. How much oversight does AI-generated code need? The answer depends on what the code does, who it serves, and what breaks if it's wrong.

For a personal side project? Vibe code all day. For a government platform that handles citizen data and financial transactions? Every line needs a human set of eyes. At MVP Apps, we sit somewhere in the middle — using AI aggressively for speed while maintaining the review discipline that keeps our clients' systems reliable.

I've been writing code since 2017. The tools change, the speed changes, but the responsibility doesn't. If your name is on the deployment, you own every line — whether a human wrote it or a machine did.