Supabase Client Becomes Irrecoverably Corrupted After Tab Suspension (PWA/Browser)
Summary
After a few minutes of browser tab suspension or mobile app backgrounding (especially in PWAs or on Safari/iOS), the Supabase client enters a corrupted internal state. Once this happens:
functions.invoke() hangs silently or never resolves
auth.getSession() returns null despite a valid session
auth.signOut() no longer completes
- Realtime channels fail silently and don’t reconnect
- The only reliable recovery is to fully recreate the Supabase client via
createClient()
What Works Initially
- Supabase client initializes correctly
- Auth/session is valid
- Realtime channels and function calls operate as expected
What Fails After Idle
After 2–5 minutes of backgrounding:
functions.invoke() stalls without resolving
auth.getSession() returns null, even though a session exists
- Realtime
.on('close') fires, but .on('open') never follows
realtime.accessTokenValue becomes out-of-sync with auth.getSession()
auth.getUser() may return a user, but API calls return 401s
Root Cause
Supabase client internally shares state across:
- Auth session and token
- Realtime WebSocket
- Function call promise queue
When the browser suspends or backgrounds the tab/app:
- The WebSocket may silently disconnect
- Session token state becomes desynchronized
- Promises issued via
.functions.invoke() may freeze
Because the client is monolithic, this internal corruption affects all areas of usage (auth, functions, realtime).
Current Workaround
We’ve implemented a wrapper that:
- Detects provable corruption based on 5 deterministic checks:
- Internal promise hang (
functions.invoke() hangs, raw fetch() works)
- Session desync (
auth.getSession() returns null while session exists)
- Realtime token mismatch (
realtime.accessTokenValue is stale)
- WebSocket zombie state (
.on('close') fires but never .on('open'))
- False-positive authentication (
getUser() returns a user, but requests 401)
- Replaces the client with a new one using
createClient()
- Rehydrates session
- Re-subscribes to Realtime channels
Repro Steps
- Use a PWA or leave an app open for 2-3 min and move to a different tab
- Authenticate a user and open a Realtime channel
- Background the tab for 3–5 minutes (or switch apps on mobile)
- Resume the tab/app
- Attempt to call
auth.getSession() or functions.invoke()
- Observe: client fails to complete call or responds incorrectly
Suggested Fixes
- Add public API for health:
supabaseClient.isCorrupted()
supabaseClient.reinitialize()
- Internally self-heal:
- Auto-sync tokens between
.auth and .realtime
- Auto-reconnect WebSocket with fallback validation
- Retry
.functions.invoke() if internal execution stalls
- Provide official guidance for client recreation
Impact
This issue affects:
- PWAs and mobile apps (especially on Safari and iOS)
- Long-lived browser sessions
- Background-tab use cases
- Any app requiring real-time + session persistence without full reload
Environment
- Browser(s): Chrome, Safari, Mobile Safari
- App type: [SPA / PWA / React / Svelte / etc.]
Supabase Client Becomes Irrecoverably Corrupted After Tab Suspension (PWA/Browser)
Summary
After a few minutes of browser tab suspension or mobile app backgrounding (especially in PWAs or on Safari/iOS), the Supabase client enters a corrupted internal state. Once this happens:
functions.invoke()hangs silently or never resolvesauth.getSession()returnsnulldespite a valid sessionauth.signOut()no longer completescreateClient()What Works Initially
What Fails After Idle
After 2–5 minutes of backgrounding:
functions.invoke()stalls without resolvingauth.getSession()returnsnull, even though a session exists.on('close')fires, but.on('open')never followsrealtime.accessTokenValuebecomes out-of-sync withauth.getSession()auth.getUser()may return a user, but API calls return 401sRoot Cause
Supabase client internally shares state across:
When the browser suspends or backgrounds the tab/app:
.functions.invoke()may freezeBecause the client is monolithic, this internal corruption affects all areas of usage (auth, functions, realtime).
Current Workaround
We’ve implemented a wrapper that:
functions.invoke()hangs, rawfetch()works)auth.getSession()returns null while session exists)realtime.accessTokenValueis stale).on('close')fires but never.on('open'))getUser()returns a user, but requests 401)createClient()Repro Steps
auth.getSession()orfunctions.invoke()Suggested Fixes
supabaseClient.isCorrupted()supabaseClient.reinitialize().authand.realtime.functions.invoke()if internal execution stallsImpact
This issue affects:
Environment