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

  1. Do not call queryRunner.release() until ALL queries through it have finished.
  2. Create a fresh queryRunner for each unit of work instead of reusing a released one.
  3. 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

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


AI-assisted analysis of typeorm/typeorm@04ff4daedc (2026-08-03). Data as JSON: /data/errors/b7c35e7ded274575.json. Report an issue: GitHub.