typeorm/typeorm · error · QueryRunnerProviderAlreadyReleasedError
Database connection provided by a query runner was already r
Error message
Database connection provided by a query runner was already released, cannot continue to use its querying methods anymore.
What it means
Thrown by DataSource.query() when the supplied queryRunner has `isReleased === true`. Query runners are single-use connection handles; once release() is called the underlying connection is returned to the pool and must not be used. TypeORM refuses to route a query through a released runner.
Source
Thrown at src/data-source/DataSource.ts:515
/**
* Executes raw SQL query and returns raw database results.
*
* @param query
* @param parameters
* @param queryRunner
* @returns a raw response from the database client
* @see {@link https://typeorm.io/data-source-api | Official docs} for examples.
*/
async query<T = any>(
query: string,
parameters?: any[] | ObjectLiteral,
queryRunner?: QueryRunner,
): Promise<T> {
if (InstanceChecker.isMongoEntityManager(this.manager))
throw new TypeORMError(`Queries aren't supported by MongoDB.`)
if (queryRunner?.isReleased)
throw new QueryRunnerProviderAlreadyReleasedError()
const usedQueryRunner = queryRunner ?? this.createQueryRunner()
try {
return await usedQueryRunner.query(query, parameters) // await is needed here because we are using finally
} finally {
if (!queryRunner) await usedQueryRunner.release()
}
}
/**
* Tagged template function that executes raw SQL query and returns raw database results.
* Template expressions are automatically transformed into database parameters.
* Raw query execution is supported only by relational databases (MongoDB is not supported).
* Note: Don't call this as a regular function, it is meant to be used with backticks to tag a template literal.
*
* @example
* dataSource.sql`SELECT * FROM table_name WHERE id = ${id}`View on GitHub (pinned to 04ff4daedc)
Solutions
- Do not call queryRunner.release() until ALL queries through it have finished.
- Create a fresh queryRunner for each unit of work instead of reusing a released one.
- Restructure so release() runs in a finally AFTER the last query, not before.
Example fix
// before
const qr = dataSource.createQueryRunner()
await qr.release()
await dataSource.query('SELECT 1', [], qr) // throws
// after
const qr = dataSource.createQueryRunner()
try {
await dataSource.query('SELECT 1', [], qr)
} finally {
await qr.release()
} Defensive patterns
Strategy: validation
Validate before calling
if (queryRunner.isReleased) {
throw new Error('Query runner already released; create a new one.')
}
await dataSource.query(sql, [], queryRunner) Type guard
function isUsableQueryRunner(qr: QueryRunner): boolean {
return !qr.isReleased
} Try / catch
try {
await dataSource.query(sql, [], queryRunner)
} catch (e) {
if (e instanceof QueryRunnerProviderAlreadyReleasedError) { const qr = dataSource.createQueryRunner(); /* retry on fresh runner */ }
else throw e
} Prevention
- Always release() in a finally AFTER the last query, never before.
- Never reuse a stored queryRunner across units of work.
- Create a fresh queryRunner per transaction/unit of work.
When it happens
Trigger: Passing a queryRunner to `dataSource.query(sql, params, qr)` after `await qr.release()` was already called; reusing a runner stored from a previous transaction; calling query inside a finally block after release() ran.
Common situations: Manual transaction code that releases the runner then continues querying; helper functions that close the runner before the caller finishes; double-release where the second use fails.
Related errors
- Query runner already released. Cannot run queries anymore.
- Query runner already released. Cannot run queries anymore.
- Query runner already released. Cannot run queries anymore.
- Query runner already released. Cannot run queries anymore.
- Query runner is already released. Call "createQueryRunner" o
AI-assisted analysis of typeorm/typeorm@04ff4daedc (2026-08-03).
Data as JSON: /data/errors/b7c35e7ded274575.json.
Report an issue: GitHub.