remix-run/remix · error · Error
Unknown transaction token: + token.id
Error message
Unknown transaction token: + token.id
What it means
The MySQL data-table driver tracks open transactions in an internal map keyed by transaction token id. commitTransaction throws when asked to commit a token that is not in that map — typically because the transaction was already committed, rolled back, or the token was fabricated or from another driver instance.
Source
Thrown at packages/data-table-mysql/src/lib/driver.ts:262
this.#transactions.set(token.id, {
connection,
releaseOnClose,
})
return token
}
/**
* Commits an open mysql 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 failed = false
try {
await transaction.connection.commit()
} catch (error) {
failed = true
throw error
} finally {
this.#transactions.delete(token.id)
if (transaction.releaseOnClose) {
if (failed) {
destroyMysqlConnection(transaction.connection)
} else if (isMysqlPoolConnection(transaction.connection)) {
transaction.connection.release()
}
}View on GitHub (pinned to 9696913134)
Solutions
- Commit or roll back each token exactly once; clear/ignore the token after finalization
- Ensure the token comes from the same driver instance that began the transaction
- Wrap begin/commit in a helper that guarantees single finalization (commit on success, rollback on error)
Example fix
// before
await driver.commitTransaction(token)
// ...later, retry path
await driver.commitTransaction(token) // throws
// after
let done = false
try {
await driver.commitTransaction(token)
done = true
} finally {
token = done ? token : undefined
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track finalization yourself let finalized = false // only commit when not finalized: if (!finalized) await driver.commitTransaction(token)
Type guard
type FinalizableToken = TransactionToken & { finalized?: boolean }
const canCommit = (t: FinalizableToken) => !t.finalized Try / catch
try { await driver.commitTransaction(token) } catch (e) { if (e instanceof Error && e.message.startsWith('Unknown transaction token')) { /* already finalized: ignore */ } else throw e } Prevention
- Commit/rollback exactly once per token; use a wrapper helper
- Discard token references after finalization
When it happens
Trigger: Calling driver.commitTransaction(token) twice with the same token; committing after rollbackTransaction already removed it; passing a TransactionToken obtained from a different driver instance or constructed manually; committing after the transaction errored out and auto-removed itself.
Common situations: Retry logic that re-commits on failure without checking state; long-lived token references held across driver restarts (e.g. pool closed and reopened); concurrent code paths both finalizing the same transaction.
Related errors
- MySQL database cannot + method + while transactions are op
- MySQL migration lock is already held by this database
- MySQL database + method + () requires config-based construc
- MySQL database config requires a database name
- MySQL migration lock could not be acquired
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/f399e6e54ad9df99.
Report an issue: GitHub.