This is a living report. The current numbers and method are at research.gemmein.com, updated daily.
We grouped 59 recent, verified reports from builders whose apps broke near or after launch. Most failures fall in a few places: sign-in, hand-written access rules, file storage and payment webhooks.
We read 4,469 public builder posts, judged 916 of them against a fixed definition of "stuck going live", and kept only cases backed by a quote checked against the original post. 59 verified cases fall in the last 12 months.
What it looks like: Users can't finish signing in, or sign-up stalls. Reset or email links fail or never arrive. The browser shows a signed-in user as logged out, a paying customer loses access to their account, or a token refresh signs real users out mid-session.
Why it happens: Sign-in has several parts, and different layers own them. An email has to arrive, and its link has to redirect to the right origin. A cookie needs the right domain, SameSite and Secure attributes. Refresh-token rotation can race across tabs or parallel requests, and the server has to write and read session storage. Each part works in local dev on one domain. It breaks on the production domain, in a second tab, or when a mail provider's link scanner opens a one-time link before the user does.
The fix: Test the full flow on the production domain with a fresh inbox and a second browser. Check cookie attributes and the redirect allow-list. Set up SPF and DKIM on the sending domain. Make token refresh single-flight so only one request rotates the token. Log every auth failure with a reason, not just a 401.
What it looks like: It passes every test with one account. Then someone finds they can read, change or delete other customers' rows through the public API. Anonymous visitors can write into paying customers' records, a read the client can call returns credentials, or a new account makes itself an admin.
Why it happens: Once the database key ships to the browser, row-level security is the only boundary, and someone has to write it by hand. With RLS off on a table, anyone holding the public key has full access. A policy of using (true), or one that only checks that someone is signed in, passes every single-user test. If roles come from metadata the user can edit at sign-up, or from claims the client sets, users can grant themselves admin. A security-definer function the client can call skips policies entirely.
The fix: Turn on RLS for every table in exposed schemas and deny by default. Write separate select, insert, update and delete policies tied to ownership (auth.uid()), and add with check on writes. Keep roles in a table users cannot write to, or in server-only metadata. Then test with two real accounts and an anonymous client: user B must fail to read, change or delete user A's rows.
What it looks like: Sign-up fails with a permission error. Signed-in users get empty results for their own data, or shared screens show the wrong records. Builders close to launch are stuck on their access rules and can't tell whether the rules are right.
Why it happens: RLS denies by default, and a policy is missing or doesn't match. An insert policy may exist with no select policy, so the read after the insert fails. A profile row may be inserted during sign-up before a session exists, so auth.uid() is null. A policy may compare the owner against the wrong column or id type. Claims set on the server never reach the rule that checks them. Nothing in the stack says whether the rules are correct, and most apps are only tested by one developer signed in as themselves.
The fix: Create per-user rows from a server-side trigger or backend call, not from the client during sign-up. Write policies per operation, and read the real error instead of trusting an empty array. Add a two-account test to CI that checks both what must succeed and what must fail, and run it before every deploy.
What it looks like: The customer pays and the provider shows the charge, but the app still shows the paywall. Live checkout completes and nothing happens, paid invoices never reach the app, or the webhook route returns 503.
Why it happens: Access depends on a webhook that the app has to receive, verify and act on. The signature check fails when the framework parses the body before verification (it needs the raw bytes), or when the test-mode signing secret is still set in live. The live endpoint may never have been registered or subscribed to checkout.session.completed. Missing production env vars make the handler fail. Retries arrive twice or out of order, and the handler isn't idempotent.
The fix: Verify against the raw request body using the live endpoint's own secret. Register the live endpoint and subscribe it to the events you use. Make the handler idempotent by event id and return 2xx quickly. Reconcile against the provider's subscription state on sign-in or on a schedule, so one lost event doesn't cost a customer their access.
What it looks like: Production sign-in, data reads and file loads all fail at the same moment, and every customer is locked out. On hosted app builders, there may be nobody to escalate to.
Why it happens: Auth, database and storage sit in one project on one dependency path. An exhausted quota, a d project, a rotated or expired key, an env var lost in a deploy, or an upstream incident takes all of them down together. Small apps rarely have health checks or alerts, so customers report the outage first.
The fix: Add an external uptime check that covers sign-in and one data read. Keep keys and env config in one place and check them after every deploy. Learn your provider's plan limits and rules. Show a clear error state in the app instead of a blank screen.
What it looks like: Either every upload fails for every user, or anyone can open other customers' files without signing in. Builders who ask for owner-only uploads can't get them working.
Why it happens: Storage has its own policy layer, separate from table rules and keyed on bucket and object path. A public bucket, or a policy that doesn't tie the path prefix to the user's id, exposes every file. A missing insert policy, or a path convention that differs between client and policy, blocks every upload. Once a public URL is shared, it bypasses the rules entirely.
The fix: Keep buckets private. Store objects under a path that starts with the owner's id, and write insert, select and delete policies that compare that folder to the signed-in user. Serve files through short-lived signed URLs. Test upload and download as two different users and as an anonymous visitor.
What it looks like: A subscription charge is never created, an upgrade charges the wrong amount, or the app and the payment provider disagree about who is subscribed. Some apps trust a price sent from the client.
Why it happens: A subscription is a state machine (trial, active, past due, upgrade, proration, cancel) split between the provider and the app's own table. Hand-rolled code copies that state instead of deriving it, computes proration itself, or accepts an amount or price id from the browser.
The fix: Make the provider the source of truth for subscription state, and keep only a cache keyed by customer id. Never accept a price or amount from the client; map a plan key to a price on the server. Let the provider compute proration, and preview the upgrade invoice before confirming.
Posts come from Stack Overflow, GitHub issues and discussions, Hacker News, community forums and public Discord support channels. We publish aggregates and paraphrased patterns only: no names, handles or quotes. The open data is at research.gemmein.com/data.json.