remix-run/remix · error · Error

Postgres database cannot + method + while transactions are

Error message

Postgres database cannot  + method +  while transactions are open

What it means

The Postgres driver refuses destructive/lifecycle operations (wipe, close per the callers) while any transactions are still open, to avoid corrupting in-flight work. #assertNoOpenTransactions throws this when the internal transactions map is non-empty.

Source

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

    // pg pools reject end() when called twice, so ending must be tracked to
    // keep close() idempotent.
    if (isPostgresPool(this.#client) && !this.#poolClosed) {
      this.#poolClosed = true
      await this.#client.end()
    }
  }

  #configOrThrow(method: string): PostgresPoolConfig {
    if (!this.#config) {
      throw new Error('Postgres database ' + method + '() requires config-based construction')
    }

    return this.#config
  }

  #assertNoOpenTransactions(method: string): void {
    if (this.#transactions.size > 0) {
      throw new Error('Postgres database cannot ' + method + ' while transactions are open')
    }
  }

  #maintenanceConfig(targetDatabase: string): PostgresClientConfig {
    let maintenanceDatabase = this.#maintenanceDatabase

    if (maintenanceDatabase === targetDatabase) {
      maintenanceDatabase = targetDatabase === 'postgres' ? 'template1' : 'postgres'
    }

    let config = this.#configOrThrow('maintenance')
    let connectionString = replaceDatabaseInConnectionString(
      config?.connectionString,
      maintenanceDatabase,
    )

    return { ...config, connectionString, database: maintenanceDatabase }
  }

View on GitHub (pinned to 9696913134)

Solutions

  1. Ensure every beginTransaction has a matching commit or rollback (try/finally) before wipe/close
  2. Use the managed transaction API that auto-commits/rolls back
  3. Await all in-flight request work before calling wipe() or close() in teardown

Example fix

// before
let token = await driver.beginTransaction()
// early return leaks the transaction
await driver.wipe() // throws

// after
let token = await driver.beginTransaction()
try { /* work */ await driver.commitTransaction(token) }
catch (e) { await driver.rollbackTransaction(token); throw e }
Defensive patterns

Strategy: validation

Validate before calling

// ensure no open transactions before wipe/close
if ((driver as any).hasOpenTransactions?.()) { /* drain first */ }

Try / catch

try { await driver.wipe() } catch (e) { if (e instanceof Error && e.message.includes('while transactions are open')) { await drainPendingWork(); await driver.wipe() } else throw e }

Prevention

When it happens

Trigger: Calling driver.wipe() or close() after beginTransaction without a matching commit/rollback; a leaked transaction from an early return or swallowed error; concurrent request still inside a transaction while test teardown calls wipe().

Common situations: Test teardown wiping the database while an async handler holds a transaction; error paths that begin a transaction but skip rollback; awaiting close() in a server shutdown hook with pending transactions.

Related errors


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