# 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.

Source: https://supahost.app/kb/api-call-not-returning · Updated 2026-11-11

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

```js
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 `await` inside `onAuthStateChange`.** 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

1. **Move Supabase calls out of the auth callback.** Keep the callback synchronous and defer any work:

   ```js
   supabase.auth.onAuthStateChange((event, session) => {
     if (event === 'SIGNED_IN') {
       setTimeout(async () => {
         const { data } = await supabase.from('profiles').select('*').single()
         setProfile(data)
       }, 0)
     }
   })
   ```

2. **Put a timeout on the call** so you see an error instead of waiting forever:

   ```js
   const { data, error } = await supabase
     .from('messages')
     .select('*')
     .eq('room_id', roomId)
     .abortSignal(AbortSignal.timeout(10_000))
   ```

3. **Check for waiting and long-running queries** in Studio's SQL editor:

   ```sql
   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 transaction` that's been there for minutes is a likely culprit. End it with `select pg_terminate_backend(<pid>);`. See [blocked queries](/kb/blocked-queries).

4. **Speed up the slow query** so it stops holding pool connections. Run `explain analyze` on it and add the missing index. See [improving query performance](/kb/improve-query-performance).

5. **Check the role timeouts** that apply to API requests:

   ```sql
   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 with `sudo supahost-logs app-one http --follow`.
- Check the instance itself answers: `https://acme.supahost.space` returns `{"instance":"acme","status":"ok"}` when it's up.

## Related

- [Canceling statement due to statement timeout](/kb/statement-timeout)
- [Finding queries blocked by locks](/kb/blocked-queries)
- [High latency with the Supabase client](/kb/high-latency-client)
- [Connections and timeouts](/docs/database/connections-and-timeouts)
