Sitelet https://github.com/redis/node-redis/pull/3321
Skip to content

fix(cluster): reject commands before cluster topology is ready - #3321

Merged
nkaradzhov merged 2 commits into
redis:masterfrom
GiHoon1123:fix/cluster-command-before-ready
Jul 8, 2026
Merged

nkaradzhov merged 2 commits into
redis:masterfrom
GiHoon1123:fix/cluster-command-before-ready

Conversation

@GiHoon1123

@GiHoon1123 GiHoon1123 commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes #2704.

If a command is issued on a RedisCluster client before connect() resolves (or without calling connect() at all), it throws an unclear TypeError: Cannot read properties of undefined (reading 'replicas') instead of a helpful error. This happens because the slot map (this.slots) is only populated once the initial topology discovery finishes, and command routing (getClientAndSlotNumber, getClient, getPubSubClient, getShardedPubSubClient) accessed it unconditionally.

This adds an isReady flag to RedisClusterSlots (exposed on RedisCluster alongside the existing isOpen), tracked independently of isOpen:

  • isOpen: true once connect()/.destroy() toggles it (existing behavior)
  • isReady: true only after the initial topology discovery succeeds; reset on error/destroy

A new #assertReady() guard runs before slot map access in the four command-routing entry points, throwing:

  • ClientClosedError if the client was never opened
  • ClientOfflineError if it's open but topology discovery hasn't completed yet

isReady is only touched by the initial connect()/destroy() paths — it is not affected by rediscover()/#discover(), so live topology refresh and resharding are unaffected (verified by tracing all call sites).

Testing

  • Reproduced the original crash directly against unmodified main (confirmed identical error message/stack to the issue) before writing the fix.
  • Added two tests under a new Cluster command lifecycle describe block covering both reported scenarios (never connected, and connect() in flight but not awaited) — both fail with the original TypeError on main and pass with this change. These don't require a running Redis server.
  • Ran the full cluster/index.spec.ts suite locally; the handful of unrelated failures (FUNCTION LOAD, generic Client sendCommand/multi tests) reproduce identically on unmodified main, confirmed via a side-by-side baseline run — pre-existing local flakiness, not introduced by this change.
  • npm run build and eslint on the changed files are clean.

Checklist

  • Does npm test pass with this change (including linting)? (see note above on pre-existing unrelated local flakiness)
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? — isReady is a small additive public getter; happy to add a docs mention if maintainers want one.

Note

Low Risk
Targeted lifecycle guard on cluster routing entry points; isReady is not tied to ongoing rediscover() so live topology refresh behavior stays unchanged.

Overview
Cluster clients now expose isReady and reject command routing until initial topology discovery finishes, instead of crashing with a TypeError on an empty slot map.

RedisClusterSlots tracks isReady separately from isOpen: open as soon as connect() starts, ready only after the first successful discovery. #assertReady() runs before slot-based routing (getClientAndSlotNumber, getClientForKey, pub/sub client getters), throwing ClientClosedError when never opened and ClientOfflineError while discovery is still in flight. connect() only sets ready and emits connect if the cluster is still open after discovery (so a concurrent destroy() cannot resurrect readiness); destroy() / #destroy() clear ready.

RedisCluster forwards the isReady getter. New unit tests cover commands before connect and during an in-progress connect without a live Redis.

Reviewed by Cursor Bugbot for commit 7f6fc90. Bugbot is set up for automated code reviews on this repo. Configure here.

RedisCluster commands issued before connect() resolves (or without
calling connect() at all) crashed with "Cannot read properties of
undefined (reading 'replicas')" instead of a clear error, because
the slot map is only populated after the initial topology discovery
completes.

Track readiness separately from open/closed state and reject with
ClientClosedError (never connected) or ClientOfflineError (connect
in progress) before touching the slot map.

Fixes redis#2704.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 78799f8. Configure here.

Comment thread packages/client/lib/cluster/cluster-slots.ts
connect() set #isReady = true unconditionally once discovery
resolved, and didn't reset #isReady when starting a new session. If
destroy() ran while discovery was still in flight, that in-flight
discovery could later resolve successfully and mark the (already
torn down) cluster ready again, or leak a stale true into a
subsequent connect() attempt whose own discovery hasn't finished
yet -- letting #assertReady() pass before the slot map is actually
populated.

Reset #isReady at the start of connect(), and only flip it back to
true if the cluster is still open by the time discovery resolves.

@nkaradzhov nkaradzhov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLGTM

@nkaradzhov
nkaradzhov merged commit b393c0e into redis:master Jul 8, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot read properties of undefined (reading 'replicas') [calling "get"]

2 participants