{"record":{"id":"a5ad2aa6f5c4f0f4","repo":"remix-run/remix","slug":"sqlite-commit-and-rollback-both-failed","errorCode":null,"errorMessage":"SQLite commit and rollback both failed","messagePattern":"SQLite commit and rollback both failed","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"critical","filePath":"packages/data-table-sqlite/src/lib/driver.ts","lineNumber":354,"sourceCode":"   * Commits an open sqlite transaction.\n   * @param token Transaction token to commit.\n   * @returns A promise that resolves when the transaction is committed.\n   */\n  async commitTransaction(token: TransactionToken): Promise<void> {\n    this.#assertTransaction(token)\n    try {\n      this.#database.exec('commit')\n    } catch (commitError) {\n      try {\n        this.#database.exec('rollback')\n      } catch (rollbackError) {\n        let failures: unknown[] = [commitError, rollbackError]\n        try {\n          this.#discardUncertainConnection()\n        } catch (recoveryError) {\n          failures.push(recoveryError)\n        }\n        throw new AggregateError(failures, 'SQLite commit and rollback both failed', {\n          cause: commitError,\n        })\n      }\n      throw commitError\n    } finally {\n      this.#transactions.delete(token.id)\n    }\n  }\n\n  /**\n   * Rolls back an open sqlite transaction.\n   * @param token Transaction token to roll back.\n   * @returns A promise that resolves when the transaction is rolled back.\n   */\n  async rollbackTransaction(token: TransactionToken): Promise<void> {\n    this.#assertTransaction(token)\n    try {\n      this.#database.exec('rollback')","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table-sqlite/src/lib/driver.ts#L336-L372","documentation":"During commitTransaction the SQLite driver attempts COMMIT; if that fails it tries ROLLBACK as recovery, and if both fail it discards the uncertain connection and throws an AggregateError containing all failures. It signals the connection's state is unknowable and the database file/connection was reset.","triggerScenarios":"COMMIT failing due to a deferred foreign-key violation, disk I/O error, or database lock (another connection holding an exclusive lock), followed by ROLLBACK also failing (e.g. lock persists, disk full, corrupted file).","commonSituations":"Multiple processes/connections writing the same SQLite file concurrently; disk-full or NFS-mounted database files; heavy concurrent load without WAL mode causing SQLITE_BUSY on commit.","solutions":["Enable WAL mode and busy_timeout (PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000) so writers don't deadlock","Ensure a single write connection/process for the database file; queue writes through one driver","Inspect errors[0..n] of the AggregateError for the root cause (disk, lock, corruption) and address it; if the file may be corrupt, restore from backup or run integrity_check"],"exampleFix":"// after opening the database\nawait db.exec('pragma journal_mode = wal')\nawait db.exec('pragma busy_timeout = 5000')","handlingStrategy":"try-catch","validationCode":"// prevent the common causes up front\nawait db.exec('pragma journal_mode = wal')\nawait db.exec('pragma busy_timeout = 5000')","typeGuard":null,"tryCatchPattern":"try { await db.commitTransaction(tx) } catch (e) { if (e instanceof AggregateError) { for (const inner of e.errors) console.error(inner); /* rebuild connection, do not reuse driver */ } throw e }","preventionTips":["Enable WAL and busy_timeout","Single writer connection per database file","Monitor disk space; treat AggregateError as fatal for the connection"],"tags":["sqlite","transaction","commit","aggregate-error"],"backgroundTag":"sqlite-commit-failed","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}