remix-run/remix · error · Error
Unknown transaction token: + token.id
Error message
Unknown transaction token: + token.id
What it means
The Postgres driver keeps an internal map of open transactions keyed by token id. commitTransaction throws this when the supplied TransactionToken is not in that map — i.e. the transaction was already committed/rolled back, was created by a different driver instance, or the token was fabricated.
Source
Thrown at packages/data-table-postgres/src/lib/driver.ts:230
this.#transactions.set(token.id, {
client: transactionClient,
releaseOnClose,
})
return token
}
/**
* Commits an open postgres transaction.
* @param token Transaction token to commit.
* @returns A promise that resolves when the transaction is committed.
*/
async commitTransaction(token: TransactionToken): Promise<void> {
let transaction = this.#transactions.get(token.id)
if (!transaction) {
throw new Error('Unknown transaction token: ' + token.id)
}
let failure: unknown
try {
await transaction.client.query('commit')
} catch (error) {
failure = error
throw error
} finally {
this.#transactions.delete(token.id)
if (transaction.releaseOnClose) {
if (failure === undefined) {
releasePostgresClient(transaction.client)
} else {
destroyPostgresClient(transaction.client, failure)
}
}View on GitHub (pinned to 9696913134)
Solutions
- Ensure commit/rollback is called exactly once per transaction token; clear the token reference afterwards
- Use the driver's transaction helper (e.g. database.transaction(async tx => ...)) which manages the token lifecycle instead of manual begin/commit
- Verify you are passing the token returned by beginTransaction on the same driver instance
- Guard with hasOpenTransactions/driver state checks before manual commit if lifecycle is uncertain
Example fix
// before
await driver.commitTransaction(token)
// ... later, in an error handler
await driver.commitTransaction(token) // throws
// after
let done = false
try {
await driver.commitTransaction(token)
done = true
} finally {
if (!done) await driver.rollbackTransaction(token).catch(() => undefined)
} Defensive patterns
Strategy: try-catch
Try / catch
try { await driver.commitTransaction(token) } catch (e) { if (e instanceof Error && e.message.startsWith('Unknown transaction token')) { /* already finished — treat as success */ return } throw e } Prevention
- Commit or rollback exactly once per token; null the reference after
- Prefer the managed transaction API
- Never reuse tokens across driver instances
When it happens
Trigger: Calling commitTransaction twice with the same token; committing after rollbackTransaction already removed it; using a token from one PostgresDatabase instance on another; keeping a token across driver close()/wipe() which clears transactions.
Common situations: Retry logic that re-commits on timeout; storing transaction tokens in shared state or caches; running tests that reuse a module-level driver but per-test tokens (or vice versa); committing after an error path already rolled back.
Related errors
- Postgres database cannot + method + while transactions are
- Unknown transaction token: ' + token.id
- Unknown transaction token: + token.id
- Postgres database + method + () requires config-based const
- SQLite database cannot ' + method + ' while transactions are
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/bf65de0f0708f9c8.
Report an issue: GitHub.