Back to blog

Article

Why Vibe Coding Breaks Down in Real Software Projects

8 min read

A practical look at why light prompts work for disposable prototypes but break down when they become the foundation for real software projects.

I gave an AI coding agent what I think was a reasonable prompt.

I asked it to build a small internal app for training purchase requests. The idea was simple: colleagues can request trainings, certifications, books, or AI subscriptions, and Office Management can process the request and mark it as done.

I did not ask for the whole production system. I wanted a quick version: React, NestJS, maybe PostgreSQL if needed, but nothing too heavy. I also said real auth was not needed yet, and a simple role switch would be fine.

The agent built something quickly.

At first, that is the nice part of vibe coding. You describe what you want, the agent makes assumptions, creates the structure, writes the code, and gives you something running. For prototypes and throwaway demos, this can be very useful.

But when I looked closer, the interesting part was not the code it wrote.

The interesting part was the decisions it made for me.

Main idea: Vibe coding is useful for disposable prototypes. It breaks down when the output becomes the foundation for a real software project.

What I Mean by Vibe Coding

Definition: For me, vibe coding means giving an AI agent a light description of intent, then letting it make most implementation and execution decisions on its own.

If I want to explore an idea, create a demo, or test if a workflow makes sense, vibe coding can be useful. It gives speed and helps me see something instead of staying only in discussion.

The problem starts when the prototype stops being disposable.

If the output is only used to learn, most shortcuts are acceptable. But if we treat that output as the foundation of a real application, every shortcut becomes something we need to understand, clean, replace, or justify.

Not because the AI writes bad code. The real problem is that the AI can make too many hidden decisions.

The Prompt Looked Reasonable

I wanted to compare what happens when the agent is given freedom.

The important part looked like this:

Use React for the frontend and NestJS for the backend, maybe PostgreSQL if needed, but don't spend too much time on setup. Just make it work locally first.

I need something simple:
- a page where a colleague can create a request
- a list of requests
- a way for office management to change the status
- maybe some comments when something is rejected or waiting for the colleague
- make the UI clean and modern

For login, don't implement real auth now, maybe add a simple way to switch between colleague and office management.

If something is unclear, just make a reasonable assumption and continue. We can fix it later.

This is the type of prompt many of us write when moving fast.

It does not look wrong. It gives the agent intent, stack preferences, some features, and permission to make assumptions.

But that last part is where the issue starts: "make a reasonable assumption and continue."

Reasonable according to what?

According to speed? Local setup? Long-term maintainability? Production architecture? User workflow? Security? Testing? The prompt did not say.

So the agent chose.

Prompt risk: When we ask the agent to make reasonable assumptions, we also need to define what "reasonable" means.

The Agent Chose File Storage

The first decision was persistence.

I said "maybe PostgreSQL if needed" and "don't spend too much time on setup." The agent interpreted that as permission to skip PostgreSQL and use a small file-backed data store.

Its reasoning was clear: the repo was empty, and file storage would keep local setup fast.

This was not irrational. For a quick demo, it makes sense.

But it was also not the architecture I had in mind for the real app. In the more serious version of the project, PostgreSQL and Prisma were part of the expected stack. The data model matters because this is not only a form and a list. It includes request ownership, statuses, Office Management assignment, comments, recurring subscriptions, and later audit-like behavior.

This is the first lesson.

When the prompt says "maybe PostgreSQL" and "keep setup fast", the agent optimizes for speed instead of architectural intent.

The agent did not fail. It followed the incentive I gave it.

Lesson: A vague technical preference is not an architecture decision. If the database matters, the prompt or spec has to say that clearly.

The UI Looked Generic

The second decision was product shape.

The vibe-coded UI had a title, a create request form, a list of requests, some status counters, a refresh button, and a switch between colleague and Office Management.

It was clean enough. It looked like an app.

But it did not feel shaped by the workflow.

The colleague side and Office Management side were in the same screen. The role switcher made it easy to demo both perspectives, but it also hid an important product decision. In a real app, these are not just visual modes. They are different user spaces with different responsibilities.

The colleague wants to create requests, track status, see renewable subscriptions, and manage accounts.

Office Management wants a queue, assignment, processing status, waiting states, maybe a board view, and ways to claim or filter work.

In the vibe-coded app, those distinctions were flattened.

Again, the agent did not fail. I asked for a simple role switch. It gave me one.

But this shortcut has a cost. Once we collapse roles into a switch, we also delay questions about ownership, permissions, navigation, and realistic workflows.

This is why "clean and modern UI" is not enough.

For product software, the UI needs workflow constraints. Otherwise, the agent can produce something that looks fine but teaches the wrong mental model.

UI note: "Clean and modern" describes style. It does not describe the workflow, roles, permissions, or product model.

The App Had No Real User Model

Because I asked not to implement auth, the agent also had no multi-user model.

For a prototype, that is acceptable.

But for this type of app, identity is not a small detail. It affects what a colleague can see, which requests belong to them, who can process requests, who can claim work, and what Office Management can update.

The shortcut starts as "no auth for now". Then it becomes no requester-specific queue, no realistic assignment, no real Office Management boundary, and no meaningful authorization behavior to verify.

Shortcut warning: Some shortcuts stay local. Others spread into the domain model.

Verification Was Too Weak

The most concrete failure was a browser-visible error.

The app showed an internal server error when loading requests. The browser console showed a 500 error for the requests endpoint.

The agent did not catch it.

Part of the reason was that browser verification was not available in that session, so it fell back to endpoint checks and dev-server checks. It also cleaned up local JSON state after verification so the app would open with an empty queue.

This is a very important point.

A dev server responding is not the same as the app working.

An API endpoint check is not the same as the user workflow working.

And an agent saying verification passed is only meaningful if the verification actually covers the risk.

In this case, the browser had the truth. The user-facing app was showing an error. The weaker checks missed it.

This is where a harness starts to matter.

If the task changes UI behavior, browser verification should be part of the expected workflow. If the app has persistence, we need a seed and reset strategy. If the app has roles, we need role-specific checks. If the app has a request lifecycle, we need to test that lifecycle, not only whether the server starts.

Verification rule: The check has to match the risk. Server checks do not prove browser workflows.

The Problem Was Silent Decisions

The easy conclusion would be: vibe coding is bad.

I do not think that is the right conclusion.

The agent built a useful prototype quickly. That has value. If my goal was only to explore the idea, I got something useful.

The real issue is different.

The agent made decisions that were reasonable for a prototype, but risky as a foundation:

  • Persistence: file-backed storage instead of PostgreSQL
  • UI shape: generic UI instead of workflow-shaped UI
  • Role model: a role switcher instead of separate user spaces
  • Identity: no real multi-user model
  • Verification: endpoint-level checks while the browser still showed an error

These are not only implementation details.

They are product, architecture, data, testing, and UI decisions.

If I notice them early, I can throw away the prototype and start clean. That is fine.

If I notice them late, after more features are built on top, cleaning the app may become more expensive than rewriting it with better constraints from the start.

This is the cost curve I care about.

Vibe coding is cheap when the output is disposable. It becomes expensive when we keep building on top of decisions we did not intentionally make.

What Specs Changed

In the spec-driven version of the same app, the result had a different shape.

It was not only about better code. The product itself was different.

The colleague-facing side had its own navigation: requests, new request, renewable requests, accounts, and analytics.

The Office Management side had its own queue and board. It exposed work assignment, requesters, statuses, filtering, renewable subscriptions, and claim actions.

Even the local login shortcut was treated differently. There was a local persona selector for development and testing, but it was scoped to the login page. It was not presented as the product's real role model.

Spec-driven development does not mean avoiding shortcuts. It means naming them and putting them in the right place.

The same idea applies to storage, verification, UI, and workflow.

When the spec says what matters, the agent has a smaller decision space. It can still write the code, but it is less likely to silently choose a different product.

Spec effect: The spec does not replace the agent. It reduces the decisions the agent is allowed to make silently.

The Takeaway

My takeaway from this experiment is simple.

Vibe coding is useful when I want a quick prototype that I can throw away.

It breaks down when I treat that prototype as the foundation of a real software project.

The issue is not that the AI is bad. The issue is that a light prompt gives the agent too much decision space. It will fill the gaps with assumptions, and many of those assumptions can be reasonable for speed but wrong for the actual system.

So the next question is not whether we should use AI coding agents.

We should.

The better question is:

If a light prompt gives the agent too much decision space, what should replace the prompt?

For me, this is where specs start to matter.