remix-run/remix · error · Error

Unknown transaction token: ' + token.id

Error message

Unknown transaction token: ' + token.id

What it means

The internal #assertTransaction guard runs before any token-scoped SQLite operation (execute, executeScript, hasTable, hasColumn, commit, rollback) and throws when the token is unknown to this driver — the transaction already finished, the driver was closed and reopened state, or the token belongs elsewhere.

Source

Thrown at packages/data-table-sqlite/src/lib/driver.ts:464

  #configOrThrow(method: string): SqliteDatabaseConfig {
    if (!this.#config) {
      throw new Error('SQLite database ' + method + '() requires config-based construction')
    }

    return this.#config
  }

  #assertNoOpenTransactions(method: string): void {
    if (this.#transactions.size > 0) {
      throw new Error('SQLite database cannot ' + method + ' while transactions are open')
    }
  }

  #assertTransaction(token: TransactionToken): void {
    this.#assertDatabaseOpen()
    if (!this.#transactions.has(token.id)) {
      throw new Error('Unknown transaction token: ' + token.id)
    }
  }

  #assertDatabaseOpen(): void {
    if (!this.#databaseOpen) {
      throw new Error('SQLite database is closed')
    }
  }
}

const REMOVE_RETRIES = 10
const REMOVE_RETRY_DELAY_MS = 100

async function removeDatabaseFile(filename: string): Promise<void> {
  // Windows keeps a just-closed database file locked for a short window (deferred handle
  // release, antivirus scans), so removal is retried with a linear backoff
  for (let attempt = 0; ; attempt++) {
    try {

View on GitHub (pinned to 9696913134)

Solutions

  1. Use the managed transaction API so token lifetime is enforced by scope
  2. Clear token references after commit/rollback; make cleanup idempotent by catching unknown-token errors
  3. Only use tokens issued by the same driver instance in the same session

Example fix

// before
try { await db.commitTransaction(tx) }
catch { await db.rollbackTransaction(tx) } // may throw 'Unknown transaction token'

// after
try { await db.commitTransaction(tx) }
catch { await db.rollbackTransaction(tx).catch(() => undefined) }
Defensive patterns

Strategy: try-catch

Try / catch

await db.rollbackTransaction(tx).catch((e) => { if (!(e instanceof Error && e.message.startsWith('Unknown transaction token'))) throw e })

Prevention

When it happens

Trigger: Using a token after commit/rollback consumed it; passing a token to a different SqliteDatabase instance; calling token-scoped methods after the database was closed (the assert checks open state first, so a closed DB reports 'SQLite database is closed' instead).

Common situations: Double rollback in catch+finally; fire-and-forget queries outliving the transaction; sharing tokens across modules/tests; retries that reuse stale tokens.

Related errors


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