n8n-io/n8n · error · TypeORMError

SQLite only supports SERIALIZABLE and READ UNCOMMITTED isola

Error message

SQLite only supports SERIALIZABLE and READ UNCOMMITTED isolation

What it means

TypeORMError('SQLite only supports SERIALIZABLE and READ UNCOMMITTED isolation') is raised in startTransaction when an isolationLevel is requested that is neither 'READ UNCOMMITTED' nor 'SERIALIZABLE'. SQLite's isolation model only exposes these two levels, so requesting READ COMMITTED or REPEATABLE READ is rejected.

Source

Thrown at packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts:86

	/**
	 * Starts transaction.
	 */
	async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
		if (this.driver.transactionSupport === 'none')
			throw new TypeORMError(
				`Transactions aren't supported by ${this.connection.driver.options.type}.`,
			);

		if (this.isTransactionActive && this.driver.transactionSupport === 'simple')
			throw new TransactionAlreadyStartedError();

		if (
			isolationLevel &&
			isolationLevel !== 'READ UNCOMMITTED' &&
			isolationLevel !== 'SERIALIZABLE'
		)
			throw new TypeORMError(`SQLite only supports SERIALIZABLE and READ UNCOMMITTED isolation`);

		this.isTransactionActive = true;
		try {
			await this.broadcaster.broadcast('BeforeTransactionStart');
		} catch (err) {
			this.isTransactionActive = false;
			throw err;
		}

		if (this.transactionDepth === 0) {
			this.transactionDepth += 1;
			if (isolationLevel) {
				if (isolationLevel === 'READ UNCOMMITTED') {
					await this.query('PRAGMA read_uncommitted = true');
				} else {
					await this.query('PRAGMA read_uncommitted = false');
				}
			}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Pass undefined (no isolation level) or one of 'READ UNCOMMITTED' / 'SERIALIZABLE' when running against sqlite.
  2. Make the isolation level configurable per-environment and omit it for sqlite.
  3. Use postgres for environments that genuinely need READ COMMITTED / REPEATABLE READ semantics.
  4. Guard the call: check the driver type before requesting a level.

Example fix

// before
await qr.startTransaction('READ COMMITTED');  // fails on sqlite
// after
const level = driver.options.type.startsWith('sqlite') ? undefined : 'READ COMMITTED';
await qr.startTransaction(level);
Defensive patterns

Strategy: validation

Validate before calling

const SQLITE_LEVELS = new Set(['READ UNCOMMITTED', 'SERIALIZABLE']);
function pickIsolationLevel(level, driverType): string | undefined {
  if (!level) return undefined;
  if (driverType.startsWith('sqlite') && !SQLITE_LEVELS.has(level)) return undefined;
  return level;
}

Type guard

const SQLITE_ISOLATION_LEVELS = ['READ UNCOMMITTED', 'SERIALIZABLE'] as const;
function isSqliteIsolationLevel(x: unknown): x is (typeof SQLITE_ISOLATION_LEVELS)[number] {
  return typeof x === 'string' && (SQLITE_ISOLATION_LEVELS as readonly string[]).includes(x);
}

Try / catch

import { TypeORMError } from '@n8n/typeorm';
try {
  await qr.startTransaction(requestedLevel);
} catch (e) {
  if (e instanceof TypeORMError && /SQLite only supports/.test(e.message)) {
    // retry without an isolation level on sqlite
    await qr.startTransaction(undefined);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Caller invokes startTransaction('READ COMMITTED') or startTransaction('REPEATABLE READ') against a sqlite-family driver. The guard fires before any SQL is issued.

Common situations: Code written for postgres (which accepts the full isolation ladder) is reused against sqlite; a generic helper passes a default isolation level without driver-awareness; tests run against sqlite but production uses postgres.

Related errors


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