{"record":{"id":"4d0185ecc6da4a52","repo":"n8n-io/n8n","slug":"transaction-rollback-failed-4d0185","errorCode":null,"errorMessage":"Transaction rollback failed","messagePattern":"Transaction rollback failed","errorType":"exception","errorClass":"TransactionRollbackFailedError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/typeorm/src/driver/sqlite-pooled/SqliteReadWriteQueryRunner.ts","lineNumber":149,"sourceCode":"\t}\n\n\t/**\n\t * Rollbacks transaction.\n\t * Error will be thrown if transaction was not started.\n\t */\n\tasync rollbackTransaction(): Promise<void> {\n\t\tif (!this.isTransactionActive) throw new TransactionNotStartedError();\n\t\tif (!this.trxDbLease) throw new TransactionNotStartedError();\n\n\t\ttry {\n\t\t\tawait this.broadcaster.broadcast('BeforeTransactionRollback');\n\n\t\t\tawait this.runQueryWithinConnection(this.trxDbLease.connection, 'ROLLBACK');\n\n\t\t\tawait this.broadcaster.broadcast('AfterTransactionRollback');\n\t\t} catch (rollbackError) {\n\t\t\tthis.trxDbLease.markAsInvalid();\n\t\t\tthrow new TransactionRollbackFailedError(rollbackError);\n\t\t} finally {\n\t\t\tthis.releaseTrxDbLease();\n\t\t}\n\t}\n\n\t/**\n\t * Executes a given SQL query.\n\t */\n\tasync query(query: string, parameters?: unknown[], useStructuredResult = false): Promise<any> {\n\t\tif (!this.connection.isInitialized) {\n\t\t\tthrow new ConnectionIsNotSetError('sqlite');\n\t\t}\n\n\t\tif (this.trxDbLease) {\n\t\t\treturn await this.runQueryWithinConnection(\n\t\t\t\tthis.trxDbLease.connection,\n\t\t\t\tquery,\n\t\t\t\tparameters,","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/typeorm/src/driver/sqlite-pooled/SqliteReadWriteQueryRunner.ts#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect `error.cause` for the real SQLite code/message and address that root cause.","Keep the DB file on stable local storage for the process lifetime.","Fix the offending statement that left the transaction in a bad state.","Re-open the DataSource / restart the process — the lease is released and marked invalid; do not reuse the runner."],"exampleFix":"// before\nawait queryRunner.rollbackTransaction();\n\n// after\ntry {\n  await queryRunner.rollbackTransaction();\n} catch (e) {\n  if (e instanceof TransactionRollbackFailedError) {\n    logger.error('rollback failed', { cause: e.cause });\n    await dataSource.destroy(); // connection is unusable\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// No safe pre-check: rollback fails at the SQL layer.\n// Keep the connection healthy:\nif (!queryRunner.connection.isInitialized) {\n  throw new Error('DataSource not initialized — cannot rollback');\n}","typeGuard":"import { TypeORMError } from '@n8n/typeorm';\n\nfunction isTransactionRollbackFailedError(e: unknown): e is TypeORMError & { cause: unknown } {\n  return e instanceof TypeORMError && e.message === 'Transaction rollback failed';\n}","tryCatchPattern":"try {\n  await queryRunner.rollbackTransaction();\n} catch (e) {\n  if (isTransactionRollbackFailedError(e)) {\n    logger.error('sqlite rollback failed', { cause: e.cause });\n    await dataSource.destroy().catch(() => {});\n  }\n  throw e;\n}","preventionTips":["Keep the SQLite file on local, stable storage.","Avoid long-running transactions that increase exposure to I/O or lock failures.","Fix statements that leave the transaction in a bad state.","Log error.cause — the real SQLite code is the actionable signal."],"tags":["database","transaction","sqlite"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}