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

  1. Commit or roll back each token exactly once; clear/ignore the token after finalization
  2. Ensure the token comes from the same driver instance that began the transaction
  3. 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

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


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