redis/node-redis · error · Error
Attempted execution on released RedisSentinelClient lease
Error message
Attempted execution on released RedisSentinelClient lease
What it means
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.
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
Example fix
// before
const lease = await sentinel.getClient();
await lease.release();
await lease.get('key'); // throws
// after
const lease = await sentinel.getClient();
try {
await lease.get('key');
} finally {
await lease.release();
} Defensive patterns
Strategy: validation
Validate before calling
// Track lease lifecycle with a flag.
let leaseReleased = false;
const lease = await sentinel.getClient();
try {
// all commands here
await lease.get('key');
} finally {
leaseReleased = true;
await lease.release();
}
// Guard any post-release code:
if (!leaseReleased) { await lease.get('key'); } Try / catch
try {
await lease.get('key');
} catch (e) {
if (e.message === 'Attempted execution on released RedisSentinelClient lease') {
// Lease was released — acquire a new one or use sentinel routing
}
} Prevention
- 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
When it happens
Trigger: 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').
Common situations: 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.
Related errors
- RedisSentinelClient lease already released
- already attempting to open
- pubSubProxy: didn't define node to do pubsub against
- Client Side Caching is only supported with RESP3
- Cluster already open
AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11).
Data as JSON: /api/errors/4c19fa3176de3002.
Report an issue: GitHub.
Appendix: source
Thrown at packages/client/lib/sentinel/index.ts:184
S,
RESP,
K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING
>;
}
/**
* Override the `typeMapping` command option
*/
withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING) {
return this._commandOptionsProxy('typeMapping', typeMapping);
}
async _execute<T>(
isReadonly: boolean | undefined,
fn: (client: RedisClient<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>) => Promise<T>
): Promise<T> {
if (this._self.#clientInfo === undefined) {
throw new Error("Attempted execution on released RedisSentinelClient lease");
}
return await this._self.#internal.execute(fn, this._self.#clientInfo);
}
async sendCommand<T = ReplyUnion>(
isReadonly: boolean | undefined,
args: CommandArguments,
options?: CommandOptions,
): Promise<T> {
const mergedOptions = { ...this.commandOptions, ...options };
return this._execute(
isReadonly,
client => client.sendCommand(args, mergedOptions)
);
}
/**View on GitHub (pinned to 90fd0652bc)