remix-run/remix · error · Error

Postgres migration lock was not held by the reserved connect

Error message

Postgres migration lock was not held by the reserved connection

What it means

After running migrations, the driver releases the advisory lock with pg_advisory_unlock and expects the returned `released` flag to be true. If the unlock reports it did not hold the lock, this error is thrown, indicating lock state inconsistency on the reserved connection.

Source

Thrown at packages/data-table-postgres/src/lib/driver.ts:610

  await client.query('set lock_timeout to default')

  let outcome: { status: 'success'; value: result } | { status: 'failure'; error: unknown }

  try {
    outcome = { status: 'success', value: await run(driver) }
  } catch (error) {
    outcome = { status: 'failure', error }
  }

  let unlockFailed = false
  let unlockError: unknown

  try {
    let result = await client.query('select pg_advisory_unlock(hashtext($1)) as "released"', [name])
    let row = result.rows[0] as Record<string, unknown> | undefined

    if (!toBooleanExists(row?.released)) {
      throw new Error('Postgres migration lock was not held by the reserved connection')
    }
  } catch (error) {
    unlockFailed = true
    unlockError = error
  }

  if (outcome.status === 'failure') {
    throw outcome.error
  }

  if (unlockFailed) {
    throw unlockError
  }

  return outcome.value
}

function buildSetTransactionStatement(options: TransactionOptions): string {

View on GitHub (pinned to 9696913134)

Solutions

  1. If using pgbouncer, enable session pooling (or bypass the pooler) for migration connections so advisory locks stay on one backend
  2. Avoid manually releasing advisory locks while migrations run; verify with pg_locks before intervening
  3. Retry the migration run; if persistent, restart the app/pooler to reset session state

Example fix

# pgbouncer.ini — migrations need session-level affinity
pool_mode = session
Defensive patterns

Strategy: validation

Validate before calling

// with pgbouncer, verify session pooling for the migration role
select * from pg_locks where locktype = 'advisory';

Try / catch

catch (e) { if (e instanceof Error && e.message.includes('was not held by the reserved connection')) { /* reset connections and retry once */ } throw e }

Prevention

When it happens

Trigger: The advisory lock was already released on that connection (double unlock, session reset); a superuser or script unlocked the lock mid-migration; connection pooler (pgbouncer in transaction mode) routing the unlock to a different server connection than the lock.

Common situations: PgBouncer transaction pooling breaking session-level advisory locks; a manual pg_advisory_unlock run during a stuck migration; prior migration crash leaving inconsistent session state that a pooler recycled.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/bd048806462ec64c. Report an issue: GitHub.