{"record":{"id":"cc5828e1cc020469","repo":"n8n-io/n8n","slug":"database-connection-provided-by-a-query-runner-was","errorCode":null,"errorMessage":"Database connection provided by a query runner was already released, cannot continue to use its querying methods anymore.","messagePattern":"Database connection provided by a query runner was already released, cannot continue to use its querying methods anymore\\.","errorType":"exception","errorClass":"QueryRunnerProviderAlreadyReleasedError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/typeorm/src/entity-manager/EntityManager.ts","lineNumber":136,"sourceCode":"\tasync transaction<T>(\n\t\tisolationOrRunInTransaction: IsolationLevel | ((entityManager: EntityManager) => Promise<T>),\n\t\trunInTransactionParam?: (entityManager: EntityManager) => Promise<T>,\n\t): Promise<T> {\n\t\tconst isolation =\n\t\t\ttypeof isolationOrRunInTransaction === 'string' ? isolationOrRunInTransaction : undefined;\n\t\tconst runInTransaction =\n\t\t\ttypeof isolationOrRunInTransaction === 'function'\n\t\t\t\t? isolationOrRunInTransaction\n\t\t\t\t: runInTransactionParam;\n\n\t\tif (!runInTransaction) {\n\t\t\tthrow new TypeORMError(\n\t\t\t\t`Transaction method requires callback in second parameter if isolation level is supplied.`,\n\t\t\t);\n\t\t}\n\n\t\tif (this.queryRunner && this.queryRunner.isReleased)\n\t\t\tthrow new QueryRunnerProviderAlreadyReleasedError();\n\n\t\t// if query runner is already defined in this class, it means this entity manager was already created for a single connection\n\t\t// if its not defined we create a new query runner - single connection where we'll execute all our operations\n\t\tconst queryRunner = this.queryRunner || this.connection.createQueryRunner();\n\n\t\ttry {\n\t\t\tawait queryRunner.startTransaction(isolation);\n\t\t\tconst result = await runInTransaction(queryRunner.manager);\n\t\t\tawait queryRunner.commitTransaction();\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\ttry {\n\t\t\t\t// we throw original error even if rollback thrown an error\n\t\t\t\tawait queryRunner.rollbackTransaction();\n\t\t\t} catch (rollbackError) {}\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tif (!this.queryRunner)","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/typeorm/src/entity-manager/EntityManager.ts#L118-L154","documentation":"QueryRunnerProviderAlreadyReleasedError fires when EntityManager.transaction() (or any path) is invoked on a query runner whose isReleased flag is already true — i.e. release() was called on it earlier. The driver/connection underneath is gone, so further querying is impossible. The class is a TypeORMError subclass produced in EntityManager at line 136.","triggerScenarios":"Calling `manager.transaction()` (or any manager method) after `queryRunner.release()` was already invoked; reusing an EntityManager created from a single-connection query runner past its lifetime; closing the DataSource while a transaction is queued; holding a manager in a long-lived singleton that outlives its query runner.","commonSituations":"Manual query-runner usage where release() is called in a finally block and then code falls through to another manager call; DI of an EntityManager that was tied to a request-scoped query runner that the request already torn down; a Promise.race where the losing branch released the runner; integration tests sharing a runner across cases.","solutions":["Audit every code path that calls `queryRunner.release()` and ensure no manager/queryRunner method runs after it (move release() to the true end of the unit of work).","Use the callback form `manager.transaction(async (em) => ...)` so TypeORM owns release; do not release the inner query runner yourself.","If you create the queryRunner manually (`connection.createQueryRunner()`), release it exactly once in a `finally` and null the reference afterwards.","Avoid caching EntityManagers created from a query runner in singletons; obtain a fresh manager per unit of work."],"exampleFix":"// before\nconst qr = connection.createQueryRunner();\nawait qr.startTransaction();\nawait qr.manager.save(User, u);\nawait qr.commitTransaction();\nawait qr.release();\n// later, accidentally:\nawait qr.manager.find(User); // -> already released\n\n// after - release exactly once, no further use\nconst qr = connection.createQueryRunner();\ntry {\n  await qr.startTransaction();\n  await qr.manager.save(User, u);\n  await qr.commitTransaction();\n} catch (e) {\n  await qr.rollbackTransaction();\n  throw e;\n} finally {\n  await qr.release();\n}\n// qr is never touched again","handlingStrategy":"validation","validationCode":"// Before using a manager tied to a query runner, check its liveness\nfunction isQueryRunnerUsable(qr: QueryRunner | undefined): boolean {\n  return !!qr && !qr.isReleased;\n}\nif (!isQueryRunnerUsable(manager.queryRunner)) {\n  throw new Error('Cannot use manager: query runner already released');\n}","typeGuard":"function isReleasedError(e: unknown): boolean {\n  return e instanceof Error && /already released/.test(e.message);\n}","tryCatchPattern":"try {\n  await manager.transaction(async (em) => em.save(u));\n} catch (e) {\n  if (isReleasedError(e)) {\n    // the runner is gone — obtain a fresh one and retry, or fail the unit of work\n    throw new OperationalError('query runner released mid-flight', { cause: e });\n  }\n  throw e;\n}","preventionTips":["Prefer the callback transaction() form so TypeORM manages release.","Release manual query runners exactly once in finally, then null the reference.","Never cache an EntityManager created from a request-scoped query runner beyond the request.","Audit code paths where Promise.race or early returns could call release() twice."],"tags":["typeorm","query-runner","lifecycle","transactions"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}