remix-run/remix · critical · AggregateError
Database transaction and rollback both failed
Error message
Database transaction and rollback both failed
What it means
Inside transaction(), if the callback throws, the driver's rollbackTransaction is attempted. If the rollback itself also throws, both errors are bundled into an AggregateError so the original failure (also set as cause) is not lost. This error means the connection/transaction is in a broken state (often the connection died mid-transaction).
Source
Thrown at packages/data-table/src/lib/database.ts:916
if (!this.#token) {
let token = await this.#driver.beginTransaction(options)
let tx = Database.#createInternalDatabase(
this.#driver,
{ now: this.#now },
{
token,
savepointCounter: this.#savepointCounter,
},
)
let result: result
try {
result = await callback(tx)
} catch (error) {
try {
await this.#driver.rollbackTransaction(token)
} catch (rollbackError) {
throw new AggregateError(
[error, rollbackError],
'Database transaction and rollback both failed',
{ cause: error },
)
}
throw error
}
await this.#driver.commitTransaction(token)
return result
}
if (!this.capabilities.savepoints) {
throw new DataTableQueryError('Nested transactions require database savepoint support')
}
let savepointName = 'sp_' + String(this.#savepointCounter.value)
this.#savepointCounter.value += 1View on GitHub (pinned to 9696913134)
Solutions
- Inspect error.errors[0] (the callback error) and error.errors[1] (the rollback error) to find the root cause; the rollback error usually reveals the connection problem.
- Ensure the database file/connection is available and not exclusively locked; verify close/remove doesn't race transactions.
- Make the operation idempotent and retry the whole transaction on a fresh connection if the DB was temporarily unavailable.
Example fix
// before
try { await db.transaction(cb) } catch (e) { console.log(e.message) }
// after
try {
await db.transaction(cb)
} catch (error) {
for (let inner of error.errors ?? [error]) console.error(inner)
} Defensive patterns
Strategy: retry
Validate before calling
// Probe the connection before starting a transaction
await db.hasTable('some_table') // throws early if closed/unavailable
await db.transaction(cb) Try / catch
try {
await db.transaction(cb)
} catch (error) {
if (error instanceof AggregateError) {
const [callbackError, rollbackError] = error.errors
// log both; retry once if rollbackError indicates a dead connection
}
} Prevention
- Never close/remove the database while transactions are in flight.
- Make transaction callbacks idempotent to allow safe retries.
When it happens
Trigger: A transaction callback throws AND the ROLLBACK fails — connection dropped (SQLite file deleted, network DB disconnected), the transaction was already auto-rolled-back by the server, or the driver handle is closed.
Common situations: Database file removed or locked during a transaction; connection timeouts; nested error handling where the first error killed the session.
Related errors
- Nested transaction cleanup failed
- SQLite rollback and connection cleanup both failed
- Cannot call ' + method + '() from a transaction-scoped datab
- Nested transactions require database savepoint support
- Unknown transaction token: + token.id
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/f507b854842655fc.
Report an issue: GitHub.