Skip to content

fix(client): vendor cluster-key-slot to fix empty hash tag slot bug - #3431

Open
mishan wants to merge 1 commit into
redis:masterfrom
mishan:fix/cluster-key-slot-bug
Open

fix(client): vendor cluster-key-slot to fix empty hash tag slot bug#3431
mishan wants to merge 1 commit into
redis:masterfrom
mishan:fix/cluster-key-slot-bug

Conversation

@mishan

@mishan mishan commented Aug 29, 2026

Copy link
Copy Markdown

Redis treats an empty hash tag ("{}") as no tag at all: it hashes the whole key and stops searching. cluster-key-slot's single-pass scanner instead falls through and keeps scanning for the next "}", hashing whatever sits between - silently misrouting commands and, for sharded pub/sub, resolving a channel to the wrong master so subscribed messages are never delivered. Measured against a live Redis 8.0.6 cluster, this diverges from real slot assignment 460 times across an 8016-key corpus.

Upstream (invertase/cluster-key-slot) was archived in March 2026 and had been unmaintained for years before that, and @redis/client pins the exact version "1.1.2" rather than a range, so no upstream fix would reach users regardless. The function is ~40 lines plus a lookup table with no dependencies, well within vendoring range.

Vendor the implementation into packages/client/lib/utils/calculate-slot.ts with the fix applied: latch the whole-key fallback once an empty tag is seen, so a later "}" elsewhere in the key can no longer be mistaken for a tag close. lib/index.js in cluster-key-slot is byte-identical between 1.1.1 and 1.1.2, so this is the same fix applied to ioredis. Repoint cluster/index.ts, cluster-slots.ts, client/pub-sub.ts, and multi-shard-splitter.ts (plus the specs that computed expected slots) at the vendored module, drop the npm dependency, and add unit tests covering the empty-tag cases plus parity with upstream's own test suite.

A detailed write up of this bug can be found at https://github.com/mishan/intraslot/blob/main/contrib/empty-hashtag-slot-bug/README.md


Note

Medium Risk
Changes core cluster slot hashing used for all keyed routing and sharded pub/sub; behavior shifts only for keys with empty {} tags, but that path was previously wrong versus Redis.

Overview
Replaces the archived cluster-key-slot dependency with an in-tree calculate-slot helper and patches how empty hash tags ({}) are hashed so slot assignment matches Redis (whole-key hash, no further tag scanning). That bug could misroute cluster commands and sharded pub/sub channels to the wrong shard.

Cluster routing (cluster/index.ts, cluster-slots.ts), multi-shard splitting, and pub/sub slot logic now import the vendored module; cluster-key-slot is removed from @redis/client dependencies. New unit tests cover empty-tag cases, generateMulti, and parity with upstream’s hash suite. The lockfile drops cluster-key-slot and bumps workspace @redis/client to 6.2.1; root package.json adds an allowScripts entry for esbuild.

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

Redis treats an empty hash tag ("{}") as no tag at all: it hashes the whole
key and stops searching. cluster-key-slot's single-pass scanner instead
falls through and keeps scanning for the next "}", hashing whatever sits
between - silently misrouting commands and, for sharded pub/sub, resolving
a channel to the wrong master so subscribed messages are never delivered.
Measured against a live Redis 8.0.6 cluster, this diverges from real slot
assignment 460 times across an 8016-key corpus.

Upstream (invertase/cluster-key-slot) was archived in March 2026 and had
been unmaintained for years before that, and @redis/client pins the exact
version "1.1.2" rather than a range, so no upstream fix would reach users
regardless. The function is ~40 lines plus a lookup table with no
dependencies, well within vendoring range.

Vendor the implementation into packages/client/lib/utils/calculate-slot.ts
with the fix applied: latch the whole-key fallback once an empty tag is
seen, so a later "}" elsewhere in the key can no longer be mistaken for a
tag close. lib/index.js in cluster-key-slot is byte-identical between
1.1.1 and 1.1.2, so this is the same fix applied to ioredis. Repoint
cluster/index.ts, cluster-slots.ts, client/pub-sub.ts, and
multi-shard-splitter.ts (plus the specs that computed expected slots) at
the vendored module, drop the npm dependency, and add unit tests covering
the empty-tag cases plus parity with upstream's own test suite.
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.

1 participant