brianc/node-postgres · error · Error
Release called on client which has already been released to
Error message
Release called on client which has already been released to the pool.
What it means
Thrown by the pool when client.release() is called more than once on the same checked-out client. The pool wraps each client's release function with _releaseOnce (index.js:369-380), which tracks a 'released' boolean; the second invocation calls throwOnDoubleRelease() at line 374. This protects pool integrity — returning the same client twice would corrupt the idle list and let two consumers use one connection.
Source
Thrown at packages/pg-pool/index.js:27
return i === -1 ? undefined : list.splice(i, 1)[0]
}
class IdleItem {
constructor(client, idleListener, timeoutId) {
this.client = client
this.idleListener = idleListener
this.timeoutId = timeoutId
}
}
class PendingItem {
constructor(callback) {
this.callback = callback
}
}
function throwOnDoubleRelease() {
throw new Error('Release called on client which has already been released to the pool.')
}
function promisify(Promise, callback) {
if (callback) {
return { callback: callback, result: undefined }
}
let rej
let res
const cb = function (err, client) {
err ? rej(err) : res(client)
}
const result = new Promise(function (resolve, reject) {
res = resolve
rej = reject
}).catch((err) => {
// replace the stack trace that leads to `TCP.onStreamRead` with one that leads back to the
// application that created the query
Error.captureStackTrace(err)View on GitHub (pinned to c5e8c9a57b)
Solutions
- Track release state with a boolean flag and only call release() when not already released.
- Prefer pool.query() over manual pool.connect()/release() so the pool handles lifecycle automatically.
- Ensure error and success handlers are mutually exclusive (early-return after the first release).
Example fix
// before
pool.connect(async (err, client, release) => {
if (err) { release(err); return; }
try {
await client.query('SELECT 1');
release();
} catch (e) {
release(e);
}
release(); // bug: always runs
});
// after
pool.connect(async (err, client, release) => {
if (err) { release(err); return; }
try {
await client.query('SELECT 1');
release();
} catch (e) {
release(e);
}
// no unconditional release
});
// or simpler:
pool.query('SELECT 1').then(...) Defensive patterns
Strategy: validation
Validate before calling
function makeSafeRelease(release) {
let released = false;
return (err) => {
if (released) return; // silently ignore double release
released = true;
release(err);
};
}
// usage:
pool.connect((err, client, done) => {
const release = makeSafeRelease(done);
// use release() everywhere; double calls are no-ops
}); Try / catch
pool.connect(async (err, client, release) => {
if (err) throw err;
let released = false;
const safeRelease = (e) => {
if (released) return;
released = true;
release(e);
};
try {
await client.query('SELECT 1');
safeRelease();
} catch (e) {
safeRelease(e);
}
}); Prevention
- Prefer pool.query() which handles acquire/release automatically and cannot double-release.
- Wrap manual release calls in an idempotent guard so the second call is a no-op.
- Make error and success paths mutually exclusive with early returns after the first release.
- Use async/await with try/finally to ensure a single, guaranteed release.
When it happens
Trigger: pool.connect((err, client, release) => { release(); release(); }). Also: calling client.release() in an error handler and again in the success handler of the same query. Using pool.query() internally does not trigger this (it manages release itself), but manual pool.connect() with overlapping release calls does.
Common situations: An error-handling path calls release(err) and then the normal completion path also calls release(). Async control flow (e.g., a timeout race) that releases then the query callback also releases. Wrapping pooled client usage in a helper that releases on exit but the caller also releases.
AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03).
Data as JSON: /data/errors/923b90b008e0fefd.json.
Report an issue: GitHub.