n8n-io/n8n · error · TransactionRollbackFailedError

Transaction rollback failed

Error message

Transaction rollback failed

What it means

Thrown by the pooled SQLite read/write runner when the `ROLLBACK` SQL statement itself fails inside rollbackTransaction(). The original error is passed as `error.cause` (see TransactionRollbackFailedError). The runner marks the leased connection invalid and releases the lease in a finally block — so the transaction is terminated but the connection must not be reused.

Source

Thrown at packages/@n8n/typeorm/src/driver/sqlite-pooled/SqliteReadWriteQueryRunner.ts:149

	}

	/**
	 * Rollbacks transaction.
	 * Error will be thrown if transaction was not started.
	 */
	async rollbackTransaction(): Promise<void> {
		if (!this.isTransactionActive) throw new TransactionNotStartedError();
		if (!this.trxDbLease) throw new TransactionNotStartedError();

		try {
			await this.broadcaster.broadcast('BeforeTransactionRollback');

			await this.runQueryWithinConnection(this.trxDbLease.connection, 'ROLLBACK');

			await this.broadcaster.broadcast('AfterTransactionRollback');
		} catch (rollbackError) {
			this.trxDbLease.markAsInvalid();
			throw new TransactionRollbackFailedError(rollbackError);
		} finally {
			this.releaseTrxDbLease();
		}
	}

	/**
	 * Executes a given SQL query.
	 */
	async query(query: string, parameters?: unknown[], useStructuredResult = false): Promise<any> {
		if (!this.connection.isInitialized) {
			throw new ConnectionIsNotSetError('sqlite');
		}

		if (this.trxDbLease) {
			return await this.runQueryWithinConnection(
				this.trxDbLease.connection,
				query,
				parameters,

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Inspect `error.cause` for the real SQLite code/message and address that root cause.
  2. Keep the DB file on stable local storage for the process lifetime.
  3. Fix the offending statement that left the transaction in a bad state.
  4. Re-open the DataSource / restart the process — the lease is released and marked invalid; do not reuse the runner.

Example fix

// before
await queryRunner.rollbackTransaction();

// after
try {
  await queryRunner.rollbackTransaction();
} catch (e) {
  if (e instanceof TransactionRollbackFailedError) {
    logger.error('rollback failed', { cause: e.cause });
    await dataSource.destroy(); // connection is unusable
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No safe pre-check: rollback fails at the SQL layer.
// Keep the connection healthy:
if (!queryRunner.connection.isInitialized) {
  throw new Error('DataSource not initialized — cannot rollback');
}

Type guard

import { TypeORMError } from '@n8n/typeorm';

function isTransactionRollbackFailedError(e: unknown): e is TypeORMError & { cause: unknown } {
  return e instanceof TypeORMError && e.message === 'Transaction rollback failed';
}

Try / catch

try {
  await queryRunner.rollbackTransaction();
} catch (e) {
  if (isTransactionRollbackFailedError(e)) {
    logger.error('sqlite rollback failed', { cause: e.cause });
    await dataSource.destroy().catch(() => {});
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling rollbackTransaction() on a SqliteReadWriteQueryRunner when the ROLLBACK hits SQLITE_IOERR, the DB file became unreachable, or the connection lease was already invalidated by a prior statement error.

Common situations: SQLite DB file on an unreliable mount, disk full, file deleted while running, concurrent write-lock corruption, or a prior statement that broke transaction state.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/4d0185ecc6da4a52. Report an issue: GitHub.