n8n-io/n8n · error · TransactionCommitFailedError

Transaction commit failed

Error message

Transaction commit failed

What it means

Thrown by the pooled SQLite read/write runner when the `COMMIT` SQL statement itself fails inside commitTransaction(). The original error is passed as `error.cause` (see TransactionCommitFailedError). On failure the runner marks the leased connection invalid and releases the lease in a finally block, so the transaction is over but its effects are not guaranteed durable.

Source

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

	}

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

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

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

			await this.broadcaster.broadcast('AfterTransactionCommit');
		} catch (commitError) {
			this.trxDbLease.markAsInvalid();
			throw new TransactionCommitFailedError(commitError);
		} finally {
			this.releaseTrxDbLease();
		}
	}

	/**
	 * 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');

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Inspect `error.cause` for the real SQLite code (SQLITE_BUSY, SQLITE_IOERR, SQLITE_CONSTRAINTFK) and address that root cause.
  2. Widen the busy timeout / shorten transactions to reduce SQLITE_BUSY on commit.
  3. Ensure the DB file is on stable local storage.
  4. Treat the connection as spent — the lease is released and marked invalid; open a fresh transaction rather than reusing state.

Example fix

// before
await queryRunner.commitTransaction();

// after
try {
  await queryRunner.commitTransaction();
} catch (e) {
  if (e instanceof TransactionCommitFailedError) {
    logger.error('commit failed', { cause: e.cause });
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No safe pre-check: COMMIT fails at the SQL layer.
// Reduce SQLITE_BUSY by setting a busy timeout before transactions:
await queryRunner.query('PRAGMA busy_timeout = 5000');

Type guard

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

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

Try / catch

try {
  await queryRunner.commitTransaction();
} catch (e) {
  if (isTransactionCommitFailedError(e)) {
    logger.error('sqlite commit failed', { cause: e.cause });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling commitTransaction() on a SqliteReadWriteQueryRunner when the COMMIT hits SQLITE_BUSY/SQLITE_IOERR, the DB file became unreachable mid-transaction, the connection lease was invalidated by a prior statement error, or a constraint violation surfaced only at commit.

Common situations: Concurrent writers causing lock contention, DB file on an unreliable mount, disk full at commit time, or a deferred-FK violation that SQLite checks at commit.

Related errors


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