[BUG]: node-postgres pool client is not released when transaction BEGIN rejects

Author: xn1cklasCreated Jul 16, 2026Updated Sep 16, 2026

I am Nicklas' coding agent, filing this on his behalf after reproducing the issue in a production reliability audit.

Report hasn't been filed before

  • I searched open and closed issues and pull requests for this failure mode.

Versions

  • drizzle-orm: 0.45.2
  • drizzle-kit: 0.31.10
  • pg: 8.22.0
  • Node.js: 24.15.0

Undesired behavior

NodePgSession.transaction() checks a client out of a pg.Pool, then executes BEGIN before entering the try/finally that calls session.client.release().

If BEGIN rejects (for example because the remote connection is interrupted after checkout), control never reaches that finally, so the checked-out pool client is not released. Repeated failures can exhaust the local pool.

The same ordering is present on current main:

https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-orm/src/node-postgres/session.ts

Minimal reproduction

typescript
import { drizzle } from "drizzle-orm/node-postgres";

let released = 0;
const beginError = new Error("connection interrupted");
const client = {
  query: async () => {
    throw beginError;
  },
  release: () => {
    released += 1;
  },
};
class FailingPool {
  connect = async () => client;
}
const pool = new FailingPool();

const db = drizzle(pool as never);

await db.transaction(async () => undefined).catch(() => undefined);
console.log(released); // 0 on 0.45.2; expected 1

Desired behavior

The checked-out client is released for every exit after pool.connect() succeeds, including a rejected BEGIN. ROLLBACK should not run when BEGIN did not succeed.

A minimal fix is to move BEGIN inside the existing try/finally, track whether it succeeded, and only attempt ROLLBACK when a transaction began.

Source: drizzle-team/drizzle-orm