n8n-io/n8n · error · LockAcquireTimeoutError

Timeout waiting for lock SqliteWriteConnectionMutex to…

Error message

Timeout waiting for lock SqliteWriteConnectionMutex to become available

What it means

Thrown by SqliteWriteConnection when its single-writer async-mutex cannot be acquired within the configured acquireTimeout (set from the pool's acquireTimeout option). SQLite permits only one writer at a time; n8n's pooled driver serializes writes behind this mutex. The underlying async-mutex returns the E_TIMEOUT sentinel, which the driver rewrites into LockAcquireTimeoutError and attaches as the cause. The message names the specific lock ('SqliteWriteConnectionMutex').

Solutions

  1. Raise the pool acquireTimeout in the datasource config (DB_SQLITE_POOL_ACQUIRE_TIMEOUT or the relevant @n8n/typeorm sqlite pool option) so the longest legitimate write finishes inside the window.
  2. Find the slow writer: enable DB query logging or DB profiling and look for the write/migration that exceeds the timeout; add missing indexes or batch the operation.
  3. Ensure only one n8n process uses the SQLite file (SQLite is single-writer by design); move to PostgreSQL for multi-instance or high-write deployments.
  4. If running on a network/shared filesystem, move the sqlite file to local disk, or switch to Postgres.
  5. Confirm the write transaction is being committed/released (no leaked runExclusive callbacks, no un-awaited transactions).

Example fix

// before - default timeout too short for large migrations
const ds = new DataSource({
  type: 'sqlite',
  database: 'n8n.sqlite',
  // pool acquireTimeout defaulting to ~5s, large writes time out
});

// after - raise the SQLite pool acquire timeout
const ds = new DataSource({
  type: 'sqlite',
  database: 'n8n.sqlite',
  poolSize: 1,
  acquireTimeout: 60_000, // 60s window for the longest write
});
Defensive patterns

Strategy: retry

Validate before calling

// Before issuing the write, confirm no other long writer is in flight
// by checking process state; raise acquireTimeout to a safe bound.
const acquireTimeout = Math.max(
  config.DB_SQLITE_POOL_ACQUIRE_TIMEOUT ?? 5_000,
  estimatedLongestWriteMs * 2,
);
const ds = new DataSource({ type: 'sqlite', database, acquireTimeout });

Type guard

function isLockAcquireTimeoutError(e: unknown): e is import('@n8n/typeorm/error/LockAcquireTimeoutError').LockAcquireTimeoutError {
  return e instanceof Error && /Timeout waiting for lock SqliteWriteConnectionMutex/.test(e.message);
}

Try / catch

try {
  await writeRepo.save(batch);
} catch (e) {
  if (isLockAcquireTimeoutError(e)) {
    // transient contention — back off and retry, then surface if still failing
    await backoffRetry(() => writeRepo.save(batch), { retries: 3, baseMs: 200 });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any write path (INSERT/UPDATE/DELETE, migrations, schema changes) on the SQLite pooled driver while a previous write holds the mutex longer than acquireTimeout. Concretely: a long-running migration, a multi-thousand-row transaction, a query that scans a large table without an index, or a deadlock between an unfinished runExclusive callback and the pool's acquire timeout. Also fires when close() races with an in-flight write and cancel() rejects waiters.

Common situations: Default SQLite deployments of n8n (small/self-hosted) under load spikes; a manual migration on a large database; a workflow that fires many concurrent executions; another process holding the DB file lock (e.g. two n8n instances pointed at the same sqlite file, or an external sqlite3 session in WAL-interactive mode); slow disk/FS (NFS, network volumes) inflating write latency.

Understand the failure class

Related errors


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

Appendix: source

Thrown at packages/@n8n/typeorm/src/driver/sqlite-pooled/SqliteWriteConnection.ts:186

	private assertNotReleased() {
		if (this.isReleased) {
			throw new DriverAlreadyReleasedError();
		}
	}

	private captureInvariantViolated(extra: Record<string, string | boolean>) {
		const error = new InvariantViolatedError();
		console.error(
			'Invariant violated:',
			Object.keys(extra)
				.map((key) => `${key}=${extra[key]}`)
				.join(', '),
		);
		console.error(error);
	}

	private throwLockTimeoutError(cause: Error) {
		throw new LockAcquireTimeoutError('SqliteWriteConnectionMutex', {
			cause,
		});
	}
}

View on GitHub (pinned to 5ac6606e81)