Quick answer: Supabase backed games crash when client code trusts a backend state that changed: a Postgres query returns an error or empty set, row level security silently filters rows, a realtime channel drops or delivers an unexpected payload, or an edge function fails. To fix them, capture the auth session and JWT validity, the query and error, the RLS outcome, the realtime channel state, and the edge function and result. Group by that signature and Supabase crashes become reproducible.
Supabase gives indie developers a Postgres database, realtime subscriptions, authentication, storage, and edge functions, all behind a client library you call directly. Because the backend is a real relational database with row level security, Supabase backed games crash in ways tied to query results, RLS policies, and realtime channel state rather than your game loop. A query returns an error or fewer rows than expected because RLS filtered them, a realtime channel drops and reconnects with a gap, or an edge function fails. These depend on auth and policy state that local testing with a service role and clean data never reproduces. This post covers capturing the Supabase context that makes them tractable.
Postgres queries and unexpected results
The Supabase client runs queries against Postgres and returns data plus an error, and crashes come from ignoring the error or assuming a result shape. A query can return an error for a constraint violation, a type mismatch, or a timeout, and a client that reads the data without checking the error crashes on the null. Equally common is assuming a single row when the query returns zero or many. Capturing the query target table, the operation, and the returned error code at crash time locates the failure in the data layer precisely.
Postgres errors are specific and actionable, unlike a generic client null. A unique violation, a foreign key error, or a type cast failure each tells you exactly what the database rejected, and the fix follows from the code. A crash that always follows a particular constraint error is a data integrity issue, not a rendering bug, and you handle it where you build the query. Without the Postgres error attached to the crash, you see only where the client dereferenced an empty result and have no idea which database condition produced it.
Row level security and silent filtering
Supabase enforces access through row level security policies in Postgres, and RLS does not error when it denies access; it silently returns fewer or no rows. This is the most distinctive Supabase crash source: a query that works for one user returns empty for another because a policy excluded their rows, and the client crashes assuming the data was present. The crash depends entirely on the authenticated user and the policy, so it surfaces for some players and not others, which is maddening without context.
Capturing the auth user id and the table being queried at crash time lets you see whether RLS is the hidden cause. If crashes on a given query cluster around users who lack a certain role or ownership, the policy is filtering them and your client is not handling the empty result. The fix is twofold: handle the empty case gracefully and verify the policy matches intent. But you only know RLS is involved if the report carries the auth identity alongside the query, because the client side stack trace looks identical to a genuinely missing record.
Realtime subscriptions and channel state
Supabase realtime delivers Postgres changes over websocket channels, and crashes cluster around the channel lifecycle and payload shape. A channel can disconnect on a network change and reconnect with a gap in events, deliver an insert, update, or delete payload your handler does not expect, or fire after the relevant local state was torn down. Code that assumes an ordered, complete event stream crashes on the gaps and out of order delivery that real networks produce. Capturing the channel name and state at crash time exposes these.
Payload shape matters because realtime sends the changed row, and if your schema evolved or a column is nullable in ways your handler did not anticipate, the deserialization or access crashes. This is the realtime analog of a query result mismatch. Recording the channel name, the event type, the affected table, and the connection state at crash time turns a generic realtime handler crash into a clear statement about which subscription and which event broke, separating reconnection gaps from schema and shape mismatches.
Auth sessions and edge functions
Supabase auth issues a JWT that authenticates queries and realtime, and like any token it expires and refreshes. A refresh that fails, or a session that lapses during a long play session, causes subsequent queries to run unauthenticated, which interacts with RLS to silently return nothing and crash code expecting data. Capturing the JWT validity and time since refresh at crash time reveals when an auth lapse, cascading through RLS, is the real cause of what looks like a data error.
Edge functions run your server side logic and, like other function backends, fail through errors, timeouts, cold starts, and response shape changes. A function that returns an error your client treats as success, or whose response shape changed after a deploy while clients still expect the old one, crashes the client. Recording the edge function name, the error or status, and the client version at crash time distinguishes a timeout to handle, an outage, and a contract mismatch, each needing a different fix rather than a blanket retry.
Setting it up with Bugnet
Bugnet captures Supabase backed crashes with their full client stack trace and device context, and the in game report button snapshots game state automatically, so a backend driven failure arrives with the screen and the user it happened to. To make Supabase tractable, use custom fields to attach the auth user id and JWT validity, the query table and Postgres error, the realtime channel and event, and the edge function and result. Those fields convert a generic client null into a precise statement about which query, policy, channel, or function failed, all in one dashboard you can correlate with your Postgres logs.
Bugnet folds duplicate reports into a single issue with an occurrence count, which suits Supabase crashes that recur across many users and network conditions. You can see that a query crash hit 170 times, all for users without a certain role, and recognize an RLS filtering issue rather than a missing record. Filtering by auth user, table, or channel confirms the cause and verifies a fix, so you prioritize the Supabase interaction that is actually breaking the most players, including the RLS edge cases that only some accounts ever hit.
A policy aware crash workflow
Add the auth user id and JWT validity, the query and error, the realtime channel state, and the edge function context to your reporting once, in the layer wrapping your Supabase calls. Because RLS filters silently and auth lapses cascade into empty results, this context is what separates a data bug from a policy or auth bug that the client stack trace cannot reveal. After that, read every crash by asking which user and which policy state produced the result, not just where the empty data was dereferenced.
Test as different user roles, with expired JWTs, against RLS denials, with evolving realtime payloads, and against failing edge functions before each release, since those are where Supabase concentrates its crashes and a service role on clean data never goes. When grouped reports point at one role or one channel, reproduce it by authenticating as that user. Over releases your empty result handling, auth refresh, realtime defensiveness, and function error handling grow robust, and the policy and connectivity driven crashes that come with a real relational backend become a small, well understood set.
Supabase RLS fails silently by returning no rows. Capture the auth user and query alongside the crash, and a baffling per user data bug becomes an obvious policy gap.