knex/knex · error · Error

Transaction concluded with ${foreignViolations.length} forei

Error message

Transaction concluded with ${foreignViolations.length} foreign key violations

What it means

When a strict SQLite transaction temporarily disables foreign_keys (the connection had it ON and the caller requested enforceForeignCheck: false), Knex re-enables checking at commit time and runs PRAGMA foreign_key_check. If that returns any violation rows, the transaction is rejected with this error and will be rolled back. This makes 'temporarily relax FK during this transaction' safe-by-construction: data that violates constraints cannot silently escape.

Source

Thrown at lib/dialects/sqlite3/execution/sqlite-transaction.js:112

          `Refusing to create transaction: unable to change \`foreign_keys\` pragma inside a nested transaction`
        );
      }

      let maybeWrappedContainer = container;
      if (restoreForeignCheck === true) {
        // in the case where we are turning foreign key checks off for the duration of a transaction,
        // we need to assert that there are no violations once the work of the transaction has been
        // completed. this relies on the fact that Transaction._onAcquire runs the "container" promise
        // to completion before executing "COMMIT"
        maybeWrappedContainer = async (trx) => {
          const res = await container(trx);

          const foreignViolations = await this.client
            .raw(executeForeignCheck())
            .connection(conn);

          if (foreignViolations.length > 0) {
            throw new Error(
              `Transaction concluded with ${foreignViolations.length} foreign key violations`
            );
          }
          return res;
        };
      }

      try {
        // call out to the base class to actually do the work as it normally would
        // note: the await is required here! we need to resolve the promise, not
        // return it
        return await this._onAcquire(maybeWrappedContainer, conn);
      } finally {
        // set the foreign_keys pragma back to what it was before we performed the transaction
        this._restoreForeignCheck(conn, restoreForeignCheck).catch((e) => {
          // we were unable to put it back like we found it. dispose the connection and
          // allow any further queries to re-acquire a new, clean connection
          this._logAndDispose(

View on GitHub (pinned to e25d54bcb7)

Solutions

  1. Inspect PRAGMA foreign_key_check output for the affected tables and fix the offending rows (missing parents, orphaned children) before retrying.
  2. Reorder operations so parent rows exist before child rows that reference them.
  3. Run a pre-flight integrity check (PRAGMA foreign_key_check) on the source data before disabling enforcement.
  4. If violations are expected and acceptable, do not disable enforcement; instead insert in dependency order under enforceForeignCheck: true.

Example fix

// before
await knex.transaction(async (trx) => {
  await trx('child').insert({ parent_id: 999 }); // parent 999 missing
}, { enforceForeignCheck: false });
// after
await trx('parent').insert({ id: 999 });
await trx('child').insert({ parent_id: 999 });
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: ensure no FK violations exist before disabling enforcement
async function assertNoFkViolations(knex) {
  const rows = await knex.raw('pragma foreign_key_check');
  if (rows.length) throw new Error('Existing FK violations: ' + JSON.stringify(rows));
}
await assertNoFkViolations(knex);
await knex.transaction(async (trx) => { /* inserts in dependency order */ }, { enforceForeignCheck: false });

Try / catch

try {
  await knex.transaction(async (trx) => { /* ... */ }, { enforceForeignCheck: false });
} catch (e) {
  if (/foreign key violations/i.test(e.message)) {
    const violations = await knex.raw('pragma foreign_key_check');
    // repair rows listed in violations, then retry in dependency order
  } else throw e;
}

Prevention

When it happens

Trigger: Issuing knex.transaction(cb, { enforceForeignCheck: false }) (or a DDL alter path that sets enforceForeignCheck to false when not already transacting) and then INSERTing/UPDATEing rows that reference non-existent parent rows, or deleting parent rows that still have dependents. The check fires only when foreign keys were originally enabled and got turned off for the transaction.

Common situations: Bulk imports or seeding that intentionally disable FK checks for speed but contain orphaned references. Reordering dependent/parent inserts so children are inserted before parents. DDL table rebuilds (Knex's SQLite alter strategy) that copy data while FK is off and the data is inconsistent.

Related errors


AI-assisted analysis of knex/knex@e25d54bcb7 (2026-08-03). Data as JSON: /data/errors/71fd8446e5470242.json. Report an issue: GitHub.