Supabase API call hangs or never returns
Why an await on a supabase-js call can hang forever, from auth callback deadlocks to locked rows and a busy connection pool, and how to find which one you have.
A supabase-js call that never resolves is stuck in one of two places: in your app, before the request leaves, or in the database, waiting on something. The browser's Network tab tells you which.
What you see
console.log('before')
const { data, error } = await supabase.from('messages').select('*').eq('room_id', roomId)
console.log('after') // never printed
There's no error and no timeout. Sometimes it only happens after sign-in, or after the tab has been in the background.
Why it happens
The request never leaves the client (nothing in the Network tab):
- An
awaitinsideonAuthStateChange. supabase-js runs the callback while it holds its internal auth lock. If the callback awaits another Supabase call, that call waits for the same lock and both stop forever. - A promise that's never awaited or a component that unmounts first, so the result is thrown away.
The request is sent but stays pending:
- A lock in Postgres. An open transaction or a migration holds a lock on the table, and your query waits for it.
- The Data API's connection pool is full. Each project's PostgREST keeps a small pool of database connections (5 on SupaHost). If slow queries hold all of them, new requests queue. After a while they fail with 504
PGRST003("Timed out acquiring connection from connection pool."). - A very slow query with no statement timeout for that role.
How to fix it
Move Supabase calls out of the auth callback. Keep the callback synchronous and defer any work:
supabase.auth.onAuthStateChange((event, session) => { if (event === 'SIGNED_IN') { setTimeout(async () => { const { data } = await supabase.from('profiles').select('*').single() setProfile(data) }, 0) } })Put a timeout on the call so you see an error instead of waiting forever:
const { data, error } = await supabase .from('messages') .select('*') .eq('room_id', roomId) .abortSignal(AbortSignal.timeout(10_000))Check for waiting and long-running queries in Studio's SQL editor:
select pid, usename, state, wait_event_type, pg_blocking_pids(pid) as blocked_by, now() - query_start as running_for, left(query, 100) as query from pg_stat_activity where datname = current_database() and state <> 'idle' order by query_start;A row in
idle in transactionthat's been there for minutes is a likely culprit. End it withselect pg_terminate_backend(<pid>);. See blocked queries.Speed up the slow query so it stops holding pool connections. Run
explain analyzeon it and add the missing index. See improving query performance.Check the role timeouts that apply to API requests:
select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated', 'authenticator');
On SupaHost
- All projects on an instance share one Postgres server, so a heavy job in one project (a big import, a long migration) can slow queries in the others.
- Studio's Logs pages aren't available. On Business you can see the Data API's recent log lines with
sudo supahost-logs app-one rest, or follow the requests live withsudo supahost-logs app-one http --follow. - Check the instance itself answers:
https://acme.supahost.spacereturns{"instance":"acme","status":"ok"}when it's up.