{"record":{"id":"0a3aab2cbaed2dbd","repo":"redis/node-redis","slug":"the-client-is-closed-0a3aab","errorCode":null,"errorMessage":"The client is closed","messagePattern":"The client is closed","errorType":"exception","errorClass":"ClientClosedError","httpStatus":null,"severity":"error","filePath":"packages/client/lib/client/socket.ts","lineNumber":451,"sourceCode":"\n        if (this.#socket.writableNeedDrain) break;\n      }\n    } catch (err) {\n      // net.Socket.write can throw synchronously on a half-closed socket\n      // (writeAfterFIN -> EPIPE) before the 'close' event fires. The pending\n      // command has already been moved to #waitingForReply by the queue's\n      // generator, so the close handler will reject it on reconnect.\n      if (!err || (err as NodeJS.ErrnoException).code !== 'EPIPE') {\n        throw err;\n      }\n    } finally {\n      this.#socket.uncork();\n    }\n  }\n\n  async quit<T>(fn: () => Promise<T>): Promise<T> {\n    if (!this.#isOpen) {\n      throw new ClientClosedError();\n    }\n\n    this.#isOpen = false;\n    const reply = await fn();\n    this.destroySocket();\n    return reply;\n  }\n\n  close() {\n    if (!this.#isOpen) {\n      throw new ClientClosedError();\n    }\n\n    this.#isOpen = false;\n  }\n\n  destroy() {\n    if (!this.#isOpen) {","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/client/socket.ts#L433-L469","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait client.quit();\n// later, in another handler:\nawait client.quit(); // throws ClientClosedError\n\n// after\nlet shuttingDown = false;\nasync function shutdown() {\n  if (shuttingDown) return;\n  shuttingDown = true;\n  if (client.isOpen) await client.quit();\n}","handlingStrategy":"validation","validationCode":"if (client.isOpen) {\n  await client.quit();\n}","typeGuard":"function canQuit(client: RedisClientType): boolean {\n  return client.isOpen;\n}","tryCatchPattern":"try {\n  await client.quit();\n} catch (e) {\n  if (e instanceof ClientClosedError) return; // already closed, fine\n  throw e;\n}","preventionTips":["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."],"tags":["lifecycle","connection","teardown"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}