Living report · updated 25 September 2026

What breaks when
AI apps go live.

59 recent reports from builders taking AI-built apps live. 23 trace to access rules, 18 to sign-in and sessions, 14 to payments. None of them are about the AI feature itself.

59verified cases in the last 12 months
916reports judged against a fixed definition
4,136builder posts collected
34%involve sign-in
New verified cases per week · last 12 weeks
Oldest week on the left.
Where it breaks
34%sign-in
32%payments
31%data rules
3%other
The patterns

Hand-written access rules break more AI apps than anything else

01

Access rules that let the wrong person in

10 verified cases

What it looks like
The app works when you test it alone. Then someone finds that any visitor, or any signed-in user, can read, change or delete other customers' rows. That includes sessions, credentials and password hashes. In some cases a user makes themselves an admin at signup.
Why it happens
Row-level security is off on a table the browser can reach, or a policy checks only that someone is signed in, not that they own the row. The public key ships in the bundle, so every exposed table becomes a public API. Roles are read from signup metadata the user controls, and client-callable functions that run with elevated rights skip the policy completely.
The fix
Turn on RLS for every table in an exposed schema and deny by default. Write each policy against ownership (auth.uid() = owner_id), and write it separately for select, insert, update and delete. Never authorise from user-editable metadata; keep roles in a table only the server writes. Audit every function that runs with definer rights. Test from outside the app as the anonymous key, as user A and as user B.
With Gemmein
Each collection has exactly one of seven fixed, plain-English rules (private, shared, public_read, community, addressed, direct, admin_write), enforced on Gemmein's servers. The app never implements authorization, and every session reports `member`, the owner's included, so there is no role to escalate. Tenant isolation is on by default. Sign-in uses email codes, and no password database exists. The MCP server's `check_integration` runs live isolation checks against dev, aimed at the bug that passes with one user and breaks with two.
02

Sign-in that never completes

11 verified cases

What it looks like
Real customers can't sign up, sign in or recover their account. Reset links and magic links arrive broken, arrive late or don't arrive at all. Sometimes the auth backend fails on a fraction of requests, and the launch stalls because nobody can get past the first screen.
Why it happens
The site URL and allowed redirect list still point at the preview domain. Mail goes out through a shared default sender with low rate limits and poor deliverability. Email security scanners open one-time links before the user does, which uses them up. A PKCE flow started in one browser is finished in another, such as an in-app mail viewer. Auth config also differs between environments.
The fix
Set the production site URL and an exact redirect allowlist. Send auth mail from your own domain with SPF and DKIM set up. Where scanners are a problem, prefer typed codes to clickable links. Before launch, run the whole flow on the production domain from a fresh browser, with both a consumer inbox and a company inbox.
With Gemmein
Sign-in is one-time 8-digit email codes only. There are no passwords, no reset flow and no social sign-in. Codes last 10 minutes. They go out from your own verified domain, and until the domain is verified they still go out as "<App name> (via Gemmein)". Sending and verifying are each capped at 3 per email per 15 minutes.
03

A payment that clears but never grants access

9 verified cases

What it looks like
The customer pays and Stripe shows the money, but the app still shows the paywall. Checkout completes in test mode, then nothing happens in live. Some webhooks arrive and some don't, and retries don't help.
Why it happens
Signature verification runs on a parsed body instead of the raw bytes, or it uses the test-mode signing secret in live. No live-mode endpoint is registered, or it isn't subscribed to the right events. Production env vars are missing, so the handler returns 5xx and Stripe eventually gives up. Access is granted only inside the webhook, with nothing to reconcile it later.
The fix
Verify against the raw request body with the live endpoint's own secret. Register the live endpoint and subscribe it to the exact events you handle. Return 2xx quickly and process idempotently, keyed on the event id. Reconcile entitlement from the Stripe API when access is missing. Read the delivery log in the Stripe dashboard before you change any code.
With Gemmein
Subscriptions and one-off digital products go through your own Stripe via Payment Links, and you never build a webhook. Any other provider that signs its webhooks works through a relay that runs verbs like grant_access, grant_plan and grant_credits from one JSON file. Money lands in your own provider account. `entitlement_required` carries the plan key, so the app can show an upgrade prompt instead of a dead end.
04

Hand-written rules that lock out the rightful user, or that nobody trusts yet

8 verified cases

What it looks like
Signed-in users can't see or save their own data. Sign-up fails because the profile row can't be written. Or the builder knows the rules are the risk and can't launch until someone confirms they hold.
Why it happens
Under default-deny, a table gets a select policy but no insert policy, so the signup trigger or first write is refused. Insert-then-return fails because the new row is not yet readable. A policy that reads another RLS-protected table recurses or silently returns nothing. With no test harness, nobody can show the rules are right for two users at once.
The fix
Write a policy for every operation the app performs. Run signup-time writes in a privileged function with a fixed search path, not through the client. Keep cross-table checks in simple helper functions. Add CI tests that perform each operation as owner, as another user and as anonymous, and that assert on both allowed and refused.
With Gemmein
The app never implements authorization. Each collection picks one of seven server-enforced rules, so there are no policies to write per operation. Reaffirm proves your app's boundaries in CI with live calls, and `check_integration` runs isolation checks against dev and is read-only against anything real. The console is a back office for the operator, separate from the app.
05

Sessions that don't hold

7 verified cases

What it looks like
The user signs in and then looks signed out. They get kicked out mid-session, or they lose access to their own data after a token refresh. A paying customer suddenly can't reach their account.
Why it happens
Cookie attributes (Secure, SameSite, Domain) don't match the production domain, or the API sits on a different site from the app. Refresh-token rotation races across tabs or parallel requests and revokes the live token. Server and client read the session from different places. An adapter's session table doesn't match its expected schema. Server code trusts claims it never verified.
The fix
Serve the API from the same site as the app and set Secure with SameSite=Lax. Allow only one refresh in flight at a time. Verify the token on the server for every request with the provider's SDK, and read custom claims only from the verified result. Test with two tabs open, after expiry and after a deploy.
With Gemmein
Sessions last 30 days and each person gets one session. `verifySession(token)` works from any host and returns who the person is and what they hold, including credits, in one call.
06

Subscription logic rebuilt by hand

5 verified cases

What it looks like
A subscription is created but never charged, an upgrade charges the wrong amount, or the app's idea of who is subscribed drifts from Stripe's. The builder is writing proration and price checks from scratch.
Why it happens
Subscription state lives in Stripe while a hand-maintained copy lives in the app database. A subscription created as incomplete is never confirmed. Proration is calculated in app code. Prices or plan ids come from the client, where they can be changed.
The fix
Treat the payment provider as the source of truth and keep the local copy derived from it. Look up prices on the server by id and never accept an amount from the browser. Let hosted checkout and the customer portal handle plan changes, and preview the next invoice before confirming an upgrade.
With Gemmein
Subscriptions and one-off digital products go through your own Stripe via Payment Links. `verifySession(token)` returns what a person holds, and the console has rooms for subscriptions and payments. Each person's page shows live grants with their expiries.
07

File storage behind a second set of hand-written policies

5 verified cases

What it looks like
Every upload fails, or the opposite: files belonging to other customers can be fetched without signing in. Some builders stop at the question of how to keep a file visible only to its owner.
Why it happens
Storage policies are separate from table policies, and they're usually written later and tested less. A path-based rule expects a user-id folder that the upload code doesn't use. A bucket was made public to get past an error. Signed URLs are long-lived and get shared.
The fix
Use a private bucket and prefix every object path with the owner's id. Write matching insert and select policies on that prefix. Serve files through short-lived signed URLs, and test upload and download as owner, as another user and as anonymous.
With Gemmein
AI job outputs are saved as sealed files on the person, and `account.delete()` erases a person's files along with their other data. Tenant isolation is on by default.
08

The whole backend goes dark

4 verified cases

What it looks like
Auth and data endpoints stop answering together and every customer is locked out at once. The builder has no support channel and no view of what failed.
Why it happens
An inactive hosted project was paused, a quota or connection pool ran out, production env vars were missing after a deploy, or the provider had an incident. There was no outside check to notice first.
The fix
Run an external health check that exercises sign-in and one data read, and alert on it. Know your platform's pausing and quota rules. Keep production configuration in the deploy itself, not in a dashboard someone edits by hand.
With Gemmein
Rate limiting is on by default. The console includes logs, problems and alerts rooms.
This week's signals

What builders ran into.

Method

How every case is triaged.

4,136Collected

Public posts from the sources below, fetched politely, newest first.

942Scored

Rules flag posts that read like someone stuck going live.

916Judged

Claude judges each against one fixed definition, quoting the post.

208Verified

Kept only if that quote is found verbatim in the stored post.

59Last 12 months

The window every number on this page uses.

→Patterns

Grouped into failure patterns; only aggregates are published.

Sources
1,257 github906 stackoverflow892 hackernews468 discord327 cursor forum100 reddit63 netlify forum62 vercel forum61 bubble forum

We collect public posts where builders describe a problem taking an app to production: Stack Overflow, GitHub issues, Hacker News, vendor and community forums, and public Discord support channels. Each candidate is judged against a fixed definition of "stuck going live" and kept only when the judgement is backed by a verbatim quote that is checked against the stored post. Patterns are grouped from those verified cases; counts can overlap. We never publish names, handles or quotes. Numbers update daily; the sample size is always shown.