Why Vibe-Coded Apps Break in Production
A reviewer found that a regular user could promote their own account to admin in a Supabase app, while the interface kept working. What else breaks once real users arrive?

- Real users test systems that a happy-path demo skips: external email delivery, row-level authorization, asynchronous billing and metered API abuse.
- Authentication does not isolate user data, and a successful checkout page does not prove that subscription access is correct.
- A launch gate should test two user accounts, signed webhook events, failed dependencies, per-user limits and a hard provider spend control.
The launch gate comes before the launch post
An AI-built app can look finished while every consequential boundary remains untested. One Reddit user described a feature-rich chat app that appeared complete from the outside. Within 30 minutes, the reviewer said they found a private API key in shipped JavaScript, open database rules, identity stored in localStorage, and admin controls that were never enforced by the backend.
Another reviewer described a Supabase app where an ordinary account could read other users’ records, elevate its own role and alter administrator data. The interface kept working. The failure was not visible as a crash because the system was accepting actions it should have rejected.
The cases expose the engineering distinction that matters at launch: feature completion and production readiness are different deliverables.
| Boundary | Proof required before launch | Release blocker |
|---|---|---|
| Auth email | External inbox receives signup and reset messages; links complete | Missing mail, broken redirect, no delivery failure record |
| Data authorization | Signed-out, User A and User B CRUD tests behave as designed | Cross-user access or client-editable role and balance |
| Billing | Verified Stripe event changes internal access exactly once | Unsigned event, duplicate entitlement or stale state |
| AI usage | Key stays server-side; user and IP limits work; hard spend control is enabled | Client key, unbounded endpoint or alerts without enforcement |
| Dependency failure | Email, database, Stripe and model failures create visible states and records | Endless loading, false success or silent failure |
Email delivery is part of authentication
A signup form working for the developer’s address proves very little. Supabase’s documentation says its default SMTP service is not intended for production. It limits recipients to pre-authorized team addresses, applies restrictive sending limits and offers no delivery or uptime SLA.
Production email-password accounts, OTPs, magic links, invitations and confirmation flows need a custom SMTP provider. Supabase also recommends maintaining SPF, DKIM and DMARC records and separating authentication mail from marketing mail.
The acceptance test must leave the project team’s inbox. Use external addresses at more than one mail provider and complete signup confirmation, password reset, expiration and resend. A provider’s accepted response is not the final result; the message must arrive and the user must return to the correct app state.
| Test | What to observe |
|---|---|
| New signup | Message arrives outside the team allowlist and confirmation creates a usable session |
| Password reset | The latest link opens the correct environment and the new password works |
| Expired and repeated links | The user sees a recoverable state rather than a blank page or loop |
| Provider outage | The app records failure, explains it and gives operations a retry path |
Authentication is not authorization
Supabase Auth answers who the user is. Row Level Security decides which database rows that identity can select, insert, update and delete. A valid session does not automatically mean the user can access only their own data.
For a typical user-owned table, a policy can compare auth.uid() with an owner column. Select, insert, update and delete need deliberate policies; update policy design must account for both the existing row and the row after the change.
- A signed-out request must fail on private rows.
- User A must perform only the allowed operations on User A’s rows.
- User A must be rejected when attempting the same operations on User B’s rows.
Do not test authorization by hiding a button. A browser user controls local storage, JavaScript and network requests. Admin status, subscription access, credits, balances and moderation decisions must be enforced by trusted server or database rules.
A software engineering manager who reviewed three vibe-coded apps said each had at least one table with a missing or overly broad RLS policy. A separate chat-app reviewer found client-side identity and moderation checks that could be changed without backend approval. Both failures came from treating the client interface as an authorization boundary.
A checkout success page is not a billing system
Stripe Checkout can collect a payment while the application still grants the wrong entitlement. Stripe and the application keep separate state. The webhook handler is responsible for turning a verified payment or subscription event into the correct internal access.
Stripe requires webhook handlers to verify the raw request body, the Stripe-Signature header and the endpoint signing secret before acting on an event. If the framework parses or mutates the body first, signature verification can fail. Without verification, a forged request can trigger fulfillment, account access or record changes.
Authenticity is only the first test. Stripe can deliver an event more than once and does not guarantee generation order. Store processed event IDs, make entitlement updates idempotent, and retrieve the current Stripe object when internal state depends on facts that may have changed.
| Webhook condition | Expected behavior |
|---|---|
| Missing or invalid signature | Reject without changing access |
| Same event delivered twice | Record one state transition |
| Events arrive out of order | Reconcile against current Stripe state |
| Payment fails, is refunded or subscription ends | Change access according to current state |
| Handler times out | Return quickly, queue work and preserve retry visibility |
One Reddit launch checklist warned that AI-generated integrations omitted signature verification when the requirement was not explicit. Stripe’s documentation makes signature verification, duplicate handling and non-sequential delivery part of a correct webhook implementation.
AI usage needs product limits and provider limits
The most expensive button in an AI product is often the easiest one to test: click Generate and wait for text. Production introduces two additional actors—the automated client and the abusive user.
The API key must never ship in browser or mobile code. OpenAI instructs developers to route requests through their own backend and keep keys in server-side environment variables or a key management service. A backend proxy, however, is not a usage policy by itself.
| Control | Purpose |
|---|---|
| Server-side secret storage | Prevent extraction of the provider key from the client bundle |
| Per-user, per-IP or per-product-key rate limit | Slow loops, bots and one user monopolizing the endpoint |
| Product quota | Define how much free or paid usage each account receives |
| Enforced organization or project spend limit | Stop aggregate provider spending when application controls fail |
Provider request and token rate limits vary by model and usage tier. They protect provider capacity and do not express the product’s free-plan allowance. Spend alerts are also not caps. OpenAI distinguishes notification thresholds from enforced spend controls, and enforcement is not instantaneous, so recorded spend can slightly exceed the configured amount.
Run the endpoint in a loop under one account, across multiple accounts and without a valid session. Verify the status code, reset behavior, user-visible message, usage ledger and provider spending control. A limit that exists only in the button’s disabled state is still a client-side suggestion.
Failure handling begins after the exception is caught
The manager who reviewed three apps also reported that third-party timeouts and database write failures often disappeared after the happy path. Catching an exception is not enough if the code neither records it nor gives the user and operator a next step.
- a user-visible state that prevents blind repetition;
- a server log carrying the request, user and task identifiers;
- an idempotent retry path that cannot duplicate payment, credits or generated work;
- an operator alert when automated recovery is exhausted.
Test these outcomes by forcing dependencies to fail. Return a model-provider 429, reject a database write, delay the email provider and redeliver a Stripe event. A release test that never creates a failure has not tested recovery.
After launch, constrain the coding agent’s blast radius
One backend engineer described asking an agent for a small change and receiving a rewritten authentication flow plus three unrelated regressions. In production, edit scope therefore belongs in the release criteria.
Specify which files may change, which files must not change, what existing behavior must remain and which tests prove it. Commit before the run, inspect the resulting diff, and rerun signup, authorization, payment and AI-usage journeys when shared code changes.
The first 30 minutes and the first 24 hours
Before opening access
- Complete signup and password reset through external mail providers.
- Run signed-out, User A and User B authorization tests for every exposed table.
- Reject an invalid Stripe signature and deduplicate a repeated valid event.
- Confirm the model-provider key is absent from deployed JavaScript and mobile assets.
- Trigger per-user and per-IP limits and verify the enforced provider spend control.
- Force email, database, billing and AI dependencies to fail.
During the first day
- Watch email delivery failures and unfinished confirmations.
- Compare Stripe events, internal entitlements and refund or cancellation state.
- Review denied cross-user requests and unexpected role changes.
- Track AI requests and cost by user, endpoint and model.
- Measure signup, first completed value and return usage as separate events.
- Freeze unrelated refactors until the first production failures are understood.
Production readiness is a separate deliverable
Vibe coding compresses the time needed to reach a working interface. It also brings the operator to email deliverability, authorization, billing consistency and metered cost sooner.
The release condition is not “it works on my account.” A real user must be able to finish authentication, touch only permitted data, receive the entitlement that matches verified billing state, and consume an explicitly bounded amount of paid API capacity.
The first user does not ruin a finished app. The first user reveals which parts were still a demo.
Frequently Asked Questions
Is enabling Supabase RLS enough to protect user data?
No. Each exposed table needs grants and policies that match the intended select, insert, update and delete behavior. Test the policy while signed out and with two different user accounts.
Can an app grant paid access after the Stripe success redirect?
The redirect is not sufficient evidence. The server should verify Stripe’s signed event, prevent duplicate processing and reconcile the current payment or subscription state before changing internal access.
Does an OpenAI rate limit protect an app from a large bill?
Not by itself. Provider rate limits, application rate limits, product quotas and enforced spend limits solve different problems. Use all four, and keep the provider key on the server.
How many beta users are needed to find these failures?
Coverage matters more than a single headcount. Test external email providers, two-user authorization, invalid and duplicate webhook events, repeated AI calls and forced dependency failures.