remix-run/remix · error · Error
MySQL database cannot + method + while transactions are op
Error message
MySQL database cannot + method + while transactions are open
What it means
The driver refuses destructive lifecycle operations (wipe, close) while transactions are still open, tracked in its internal #transactions map. #assertNoOpenTransactions throws with the offending method name to prevent dropping or wiping a database with in-flight transactions that would be left in limbo.
Source
Thrown at packages/data-table-mysql/src/lib/driver.ts:485
await this.#closePool().catch(() => undefined)
if (this.#config) {
this.#client = createMysqlPool(this.#config)
this.#poolClosed = false
}
}
#configOrThrow(method: string): string | MysqlPoolOptions {
if (!this.#config) {
throw new Error('MySQL database ' + method + '() requires config-based construction')
}
return this.#config
}
#assertNoOpenTransactions(method: string): void {
if (this.#transactions.size > 0) {
throw new Error('MySQL database cannot ' + method + ' while transactions are open')
}
}
#resolveClient(token: TransactionToken | undefined): MysqlQueryable {
if (!token) {
return this.#client
}
return this.#transactionConnection(token)
}
#transactionConnection(token: TransactionToken): MysqlTransactionConnection {
let transaction = this.#transactions.get(token.id)
if (!transaction) {
throw new Error('Unknown transaction token: ' + token.id)
}
View on GitHub (pinned to 9696913134)
Solutions
- Commit or roll back every outstanding transaction before calling wipe()/close()
- Use try/finally around transaction bodies so errors always roll back
- In test teardown, drain or assert zero open transactions before wiping
Example fix
// before
const token = await driver.beginTransaction()
await doWork(driver, token)
// error path skips commit
await driver.wipe() // throws
// after
const token = await driver.beginTransaction()
try {
await doWork(driver, token)
await driver.commitTransaction(token)
} catch (e) {
await driver.rollbackTransaction(token)
throw e
}
await driver.wipe() Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure finalization in a helper
async function withTransaction<T>(driver, fn: (t: TransactionToken) => Promise<T>) {
const token = await driver.beginTransaction()
try { const r = await fn(token); await driver.commitTransaction(token); return r }
catch (e) { await driver.rollbackTransaction(token); throw e }
} Try / catch
try { await driver.wipe() } catch (e) { if (e instanceof Error && e.message.includes('while transactions are open')) { /* finalize leaked transactions, then retry */ } throw e } Prevention
- Always pair beginTransaction with try/finally finalization
- Assert no open transactions before teardown in tests
When it happens
Trigger: Calling driver.wipe() or driver.close() after beginTransaction without a matching commit/rollback; test teardown wiping the database while a leaked transaction token was never finalized.
Common situations: Test suites that begin transactions and error out before finalization, then wipe between tests; missing rollback in a catch block; async operations still in flight when close() is called during shutdown.
Related errors
- Unknown transaction token: + token.id
- 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/b8d4408c0ecf262d.
Report an issue: GitHub.