{"record":{"id":"4c19fa3176de3002","repo":"redis/node-redis","slug":"attempted-execution-on-released-redissentinelclien","errorCode":null,"errorMessage":"Attempted execution on released RedisSentinelClient lease","messagePattern":"Attempted execution on released RedisSentinelClient lease","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/sentinel/index.ts","lineNumber":184,"sourceCode":"      S,\n      RESP,\n      K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING\n    >;\n  }\n\n  /**\n   * Override the `typeMapping` command option\n   */\n  withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING) {\n    return this._commandOptionsProxy('typeMapping', typeMapping);\n  }\n\n  async _execute<T>(\n    isReadonly: boolean | undefined,\n    fn: (client: RedisClient<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>) => Promise<T>\n  ): Promise<T> {\n    if (this._self.#clientInfo === undefined) {\n      throw new Error(\"Attempted execution on released RedisSentinelClient lease\");\n    }\n\n    return await this._self.#internal.execute(fn, this._self.#clientInfo);\n  }\n\n  async sendCommand<T = ReplyUnion>(\n    isReadonly: boolean | undefined,\n    args: CommandArguments,\n    options?: CommandOptions,\n  ): Promise<T> {\n    const mergedOptions = { ...this.commandOptions, ...options };\n    return this._execute(\n      isReadonly,\n      client => client.sendCommand(args, mergedOptions)\n    );\n  }\n\n  /**","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/sentinel/index.ts#L166-L202","documentation":"A command was dispatched through RedisSentinelClient._execute after the lease was released back to the pool. After release(), the internal #clientInfo is set to undefined and the client can no longer route commands. This guard prevents using a stale handle that may have been reassigned to another operation.","triggerScenarios":"Calling any command (get, set, sendCommand, pipeline, multi) on a sentinel client lease after release() was called — e.g., const lease = await sentinel.getClient(); await lease.release(); await lease.get('key').","commonSituations":"Forgetting release() invalidates the lease; a race where release() fires in a finally block before an async command completes; passing the lease to a callback that runs after release.","solutions":["Do not use the client after calling release() — complete all work first","Track lease lifecycle explicitly and release only after all commands resolve","Prefer sentinel's built-in routing (sentinel.get('key')) over manual lease management when possible"],"exampleFix":"// before\nconst lease = await sentinel.getClient();\nawait lease.release();\nawait lease.get('key'); // throws\n\n// after\nconst lease = await sentinel.getClient();\ntry {\n  await lease.get('key');\n} finally {\n  await lease.release();\n}","handlingStrategy":"validation","validationCode":"// Track lease lifecycle with a flag.\nlet leaseReleased = false;\nconst lease = await sentinel.getClient();\ntry {\n  // all commands here\n  await lease.get('key');\n} finally {\n  leaseReleased = true;\n  await lease.release();\n}\n// Guard any post-release code:\nif (!leaseReleased) { await lease.get('key'); }","typeGuard":null,"tryCatchPattern":"try {\n  await lease.get('key');\n} catch (e) {\n  if (e.message === 'Attempted execution on released RedisSentinelClient lease') {\n    // Lease was released — acquire a new one or use sentinel routing\n  }\n}","preventionTips":["Always release leases in a finally block after all commands complete","Prefer sentinel's built-in routing (sentinel.get('key')) over manual lease management","Set the lease variable to null or a flag after release as a visual guard","Never pass a lease to async callbacks that may outlive the lease scope"],"tags":["sentinel","lease","lifecycle"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}