remix-run/remix · error · DataTableQueryError
Nested transactions require database savepoint support
Error message
Nested transactions require database savepoint support
What it means
Calling transaction() while already inside a transaction creates a nested transaction, implemented with SAVEPOINTs. The Database checks capabilities.savepoints first and throws if the driver doesn't support them, because a fake nested transaction would risk committing partial inner work.
Source
Thrown at packages/data-table/src/lib/database.ts:930
} 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 += 1
await this.#driver.createSavepoint(this.#token, savepointName)
let result: result
try {
result = await callback(this)
} catch (error) {
let failures: unknown[] = [error]
try {
await this.#driver.rollbackToSavepoint(this.#token, savepointName)
} catch (rollbackError) {
failures.push(rollbackError)
}
try {View on GitHub (pinned to 9696913134)
Solutions
- Restructure code so transactions aren't nested: accept the outer tx as a parameter instead of opening a new one (the classic 'transaction-in-transaction' refactor).
- Driver authors: implement savepoints and set capabilities.savepoints = true.
- Branch on db.capabilities.savepoints to decide whether nesting is allowed on the current driver.
Example fix
// before
async function saveThing(db, data) {
await db.transaction(async (tx) => { ... })
}
await db.transaction(async (tx) => saveThing(tx, data))
// after
async function saveThing(db, data) {
await db.transaction(async (tx) => { ... })
}
// call saveThing(db, data) at top level, not inside another transaction Defensive patterns
Strategy: validation
Validate before calling
if (depth > 0 && !db.capabilities.savepoints) {
return callback(db) // run without a nested transaction
}
return db.transaction(callback) Prevention
- Pass the outer tx into helpers instead of opening nested transactions.
- Check capabilities.savepoints before nesting.
When it happens
Trigger: db.transaction(async (tx) => { await tx.transaction(cb) }) on a driver whose capabilities.savepoints is false (e.g. a minimal custom driver or a dialect without savepoint support).
Common situations: Custom drivers that never implemented createSavepoint/releaseSavepoint; helper functions that open their own transaction being called from inside another transaction.
Related errors
- Nested transaction cleanup failed
- Cannot call ' + method + '() from a transaction-scoped datab
- createMany({ returnRows: true }) is not supported by this da
- Database transaction and rollback both failed
- Unknown transaction token: + token.id
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/620fb07a80480724.
Report an issue: GitHub.