{"record":{"id":"c6f46ec38a10eaab","repo":"redis/node-redis","slug":"the-client-is-closed","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/index.ts","lineNumber":1928,"sourceCode":"\n  /**\n   * @internal\n   */\n  async _executeMulti(\n    commands: Array<RedisMultiQueuedCommand>,\n    selectedDB?: number,\n    slotNumber?: number,\n    chainId = Symbol('MULTI Chain')\n  ) {\n    assertNoHimportSessionCommands(commands);\n\n    const dirtyWatch = this._self.#dirtyWatch;\n    this._self.#dirtyWatch = undefined;\n    const watchEpoch = this._self.#watchEpoch;\n    this._self.#watchEpoch = undefined;\n\n    if (!this._self.#socket.isOpen) {\n      throw new ClientClosedError();\n    }\n\n    if (dirtyWatch) {\n      throw new WatchError(dirtyWatch);\n    }\n\n    if (watchEpoch && watchEpoch !== this._self.socketEpoch) {\n      throw new WatchError('Client reconnected after WATCH');\n    }\n\n    const batchSize = commands.length;\n\n    return trace(CHANNELS.TRACE_BATCH,\n      async () => {\n        const typeMapping = this._commandOptions?.typeMapping;\n        const promises: Array<Promise<unknown>> = [\n          this._self.#queue.addCommand(['MULTI'], { chainId, slotNumber }),\n        ];","sourceCodeStart":1910,"sourceCodeEnd":1946,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/client/index.ts#L1910-L1946","documentation":"Thrown at the start of _executeMulti (the MULTI/EXEC transaction path) when the underlying socket is no longer open. The client was closed via close()/destroy(), or the connection was lost and not re-established, before multi.exec() could send the MULTI command. No transaction is sent to Redis.","triggerScenarios":"Calling const multi = client.multi(); ...then client.close() or client.destroy(); ...then await multi.exec(). Also fires when the socket dies mid-transaction-building and auto-reconnect is disabled or exhausted, so #socket.isOpen is false by the time exec() runs.","commonSituations":"Application shutdown logic that closes the client in a finally block while a pending multi.exec() is still awaited; reconnectStrategy set to false causing the client to stay closed after a network blip; calling exec() on a multi object created from a previous client instance that has since been torn down.","solutions":["Ensure client.close()/destroy() is not called until all in-flight multi.exec() promises have resolved (await them before shutdown).","Guard multi.exec() with if (client.isOpen) and skip or recreate the transaction when the client is closed.","If you need the transaction to survive reconnects, re-issue WATCH then MULTI/EXEC after reconnecting via await client.connect().","Reconnect the client (await client.connect()) before retrying the transaction."],"exampleFix":"// before\nconst multi = client.multi();\nmulti.set('k', 'v');\nawait client.close();\nawait multi.exec(); // throws ClientClosedError\n\n// after\nconst multi = client.multi();\nmulti.set('k', 'v');\nconst res = await multi.exec();\nawait client.close();","handlingStrategy":"validation","validationCode":"if (!client.isOpen) {\n  throw new Error('Cannot execute MULTI: client is closed. Connect or reconnect first.');\n}","typeGuard":"function canExecuteMulti(client: RedisClientType): boolean {\n  return client.isOpen;\n}","tryCatchPattern":"try {\n  await multi.exec();\n} catch (e) {\n  if (e instanceof ClientClosedError) {\n    // client closed before exec; re-connect and rebuild the transaction\n  } else throw e;\n}","preventionTips":["Never call client.close()/destroy() while a multi.exec() is pending; await it first.","Check client.isOpen before building a MULTI if the client lifecycle is uncertain.","Centralize client teardown so shutdown cannot race in-flight transactions."],"tags":["lifecycle","transactions","multi","connection"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}