{"id":"2e24d93916e07db0","repo":"knex/knex","slug":"refusing-to-create-transaction-unable-to-change","errorCode":null,"errorMessage":"Refusing to create transaction: unable to change `foreign_keys` pragma inside a nested transaction","messagePattern":"Refusing to create transaction: unable to change `foreign_keys` pragma inside a nested transaction","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/dialects/sqlite3/execution/sqlite-transaction.js","lineNumber":93,"sourceCode":"        // can leave the connection in an unexpected state. Just reject the begin transaction.\n        const error = new Error(\n          `Refusing to create transaction: failed to set \\`foreign_keys\\` pragma to the required value of ${enforceForeignCheck}`\n        );\n        error.cause = e;\n        throw error;\n      }\n\n      // if:\n      // - we're in a nested transaction\n      // - _and_ we're in strict mode\n      // - _and_ we are required to change the pragma\n      // then: we cannot continue, it's out of our hands\n      if (\n        strictForeignKeyPragma &&\n        hasOuterTransaction &&\n        restoreForeignCheck != null\n      ) {\n        throw new Error(\n          `Refusing to create transaction: unable to change \\`foreign_keys\\` pragma inside a nested transaction`\n        );\n      }\n\n      let maybeWrappedContainer = container;\n      if (restoreForeignCheck === true) {\n        // in the case where we are turning foreign key checks off for the duration of a transaction,\n        // we need to assert that there are no violations once the work of the transaction has been\n        // completed. this relies on the fact that Transaction._onAcquire runs the \"container\" promise\n        // to completion before executing \"COMMIT\"\n        maybeWrappedContainer = async (trx) => {\n          const res = await container(trx);\n\n          const foreignViolations = await this.client\n            .raw(executeForeignCheck())\n            .connection(conn);\n\n          if (foreignViolations.length > 0) {","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/knex/knex/blob/e25d54bcb707714a17f5a5744eba5c4246bb4d1d/lib/dialects/sqlite3/execution/sqlite-transaction.js#L75-L111","documentation":"SQLite ignores PRAGMA foreign_keys statements issued inside a running transaction (see sqlite.org/pragma.html#pragma_foreign_keys). Knex's strict SQLite transaction mode detects when it is nested inside an outer transaction AND the pragma actually needs to change (restoreForeignCheck is non-null) AND strict mode is on, and refuses to proceed because the requested enforcement cannot be silently guaranteed. The error is thrown before BEGIN so the connection is left clean.","triggerScenarios":"A schema-DDL operation (which runs on the strict client) is invoked while already inside an outer knex.transaction(), and the connection's current foreign_keys pragma differs from what the inner transaction requires. For example: opening knex.transaction() then inside it calling a schema builder that triggers client.ddl() (strict) which itself tries to set enforceForeignCheck.","commonSituations":"Running migrations or schema-alter calls inside an explicit outer transaction. Wrapping schema.alterTable / setNullable / dropColumn in a user-managed transaction. Pooling reuse where a prior DDL op left foreign_keys in a different state than the outer transaction expects.","solutions":["Run schema/DDL operations outside of an outer transaction, or use savepoint-free top-level transactions for DDL.","If you must nest, ensure the pragma is already in the desired state before the outer transaction begins so the inner strict transaction detects no change is needed (restoreForeignCheck stays null).","Set PRAGMA foreign_keys to the target value once at connection initialization so no in-transaction change is required."],"exampleFix":"// before\nawait knex.transaction(async (trx) => {\n  await knex.schema.withUserParams({}).alterTable('t', (b) => b.setNullable('c'));\n});\n// after\nawait knex.schema.alterTable('t', (b) => b.setNullable('c'));","handlingStrategy":"validation","validationCode":"// detect nested-transaction + strict + pragma-change risk before opening\nasync function currentFkEnabled(knex) {\n  const r = await knex.raw('pragma foreign_keys');\n  return r[0].foreign_keys === 1;\n}\nconst alreadyTransacting = !!knex.transacting;\nconst needChange = (await currentFkEnabled(knex)) !== desiredEnforce;\nif (alreadyTransacting && knex.strictForeignKeyPragma && needChange) {\n  throw new Error('Cannot change foreign_keys pragma inside a nested transaction; run DDL at the top level.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await knex.schema.alterTable('t', (b) => b.setNullable('c'));\n} catch (e) {\n  if (/nested transaction/i.test(e.message)) {\n    // move the DDL call outside the outer transaction\n  } else throw e;\n}","preventionTips":["Run schema DDL outside of explicit transactions.","Set PRAGMA foreign_keys once at pool init so in-transaction changes are never required.","Avoid wrapping migrations in a single outer transaction."],"tags":["sqlite","transactions","nested-transactions","foreign-keys","ddl"],"analyzedSha":"e25d54bcb707714a17f5a5744eba5c4246bb4d1d","analyzedAt":"2026-08-03T18:35:32.148Z","schemaVersion":2}