redis/node-redis · error · ClientClosedError
The client is closed
Error message
The client is closed
What it means
Thrown by RedisSocket.quit() when #isOpen is false. quit() sends QUIT to Redis for a graceful shutdown, which requires an active connection. Calling it on a client that is already closed (or was never connected) is a no-op that the library rejects rather than silently ignoring.
Solutions
- Guard with if (client.isOpen) await client.quit().
- Track a shutdown flag so quit()/close()/destroy() run exactly once.
- Prefer client.disconnect() for idempotent teardown if you do not need the graceful QUIT reply.
- Use a single shutdown coordinator rather than calling quit() from multiple handlers.
Example fix
// before
await client.quit();
// later, in another handler:
await client.quit(); // throws ClientClosedError
// after
let shuttingDown = false;
async function shutdown() {
if (shuttingDown) return;
shuttingDown = true;
if (client.isOpen) await client.quit();
} Defensive patterns
Strategy: validation
Validate before calling
if (client.isOpen) {
await client.quit();
} Type guard
function canQuit(client: RedisClientType): boolean {
return client.isOpen;
} Try / catch
try {
await client.quit();
} catch (e) {
if (e instanceof ClientClosedError) return; // already closed, fine
throw e;
} Prevention
- Guard quit() with if (client.isOpen).
- Use a single shutdown coordinator with a flag to prevent double-teardown.
- Prefer disconnect() if you need idempotent teardown.
When it happens
Trigger: Calling await client.quit() after already calling client.close(), client.destroy(), or a previous client.quit(). Calling quit() in an error handler that also runs after a normal shutdown path.
Common situations: Cleanup logic in finally blocks that runs after an error path already closed the client; signal handlers (SIGTERM) calling quit() on a client closed by a health-check failure; double-shutdown in test teardown.
Related errors
- Cluster already open
- Socket already opened
- The client is closed
- The client is closed
- TokenManager is not running, but a new token was received
AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11).
Data as JSON: /api/errors/0a3aab2cbaed2dbd.
Report an issue: GitHub.
Appendix: source
Thrown at packages/client/lib/client/socket.ts:451
if (this.#socket.writableNeedDrain) break;
}
} catch (err) {
// net.Socket.write can throw synchronously on a half-closed socket
// (writeAfterFIN -> EPIPE) before the 'close' event fires. The pending
// command has already been moved to #waitingForReply by the queue's
// generator, so the close handler will reject it on reconnect.
if (!err || (err as NodeJS.ErrnoException).code !== 'EPIPE') {
throw err;
}
} finally {
this.#socket.uncork();
}
}
async quit<T>(fn: () => Promise<T>): Promise<T> {
if (!this.#isOpen) {
throw new ClientClosedError();
}
this.#isOpen = false;
const reply = await fn();
this.destroySocket();
return reply;
}
close() {
if (!this.#isOpen) {
throw new ClientClosedError();
}
this.#isOpen = false;
}
destroy() {
if (!this.#isOpen) {View on GitHub (pinned to 90fd0652bc)