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.

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.

Appendix: 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)