redis/redis-py · error · RedisError

For "stable" state please use cluster_setslot_stable

Error message

For "stable" state please use cluster_setslot_stable

What it means

Raised by cluster_setslot() when the caller passes state='STABLE'. The method only handles IMPORTING, NODE, and MIGRATING states itself; the STABLE case has a dedicated method (cluster_setslot_stable) that auto-resolves the target node, so this RedisError redirects you there rather than silently guessing a node. It is a usage/API-routing error, not a server-side failure.

Source

Thrown at redis/commands/cluster.py:888

    ) -> Awaitable[bool]: ...

    def cluster_setslot(
        self, target_node: "TargetNodesT", node_id: str, slot_id: int, state: str
    ) -> bool | Awaitable[bool]:
        """
        Bind an hash slot to a specific node

        :target_node: 'ClusterNode'
            The node to execute the command on

        For more information see https://redis.io/commands/cluster-setslot
        """
        if state.upper() in ("IMPORTING", "NODE", "MIGRATING"):
            return self.execute_command(
                "CLUSTER SETSLOT", slot_id, state, node_id, target_nodes=target_node
            )
        elif state.upper() == "STABLE":
            raise RedisError('For "stable" state please use cluster_setslot_stable')
        else:
            raise RedisError(f"Invalid slot state: {state}")

    @overload
    def cluster_setslot_stable(self: SyncClientProtocol, slot_id: int) -> bool: ...

    @overload
    def cluster_setslot_stable(
        self: AsyncClientProtocol, slot_id: int
    ) -> Awaitable[bool]: ...

    def cluster_setslot_stable(self, slot_id: int) -> bool | Awaitable[bool]:
        """
        Clears migrating / importing state from the slot.
        It determines by it self what node the slot is in and sends it there.

        For more information see https://redis.io/commands/cluster-setslot
        """

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Replace the cluster_setslot(..., state='STABLE') call with cluster_setslot_stable(slot_id), which needs no node_id or target_node.
  2. If you must branch on state in code, special-case STABLE to call cluster_setslot_stable and pass only IMPORTING/NODE/MIGRATING to cluster_setslot.
  3. Verify you actually need STABLE at all during a reshard — it is usually invoked automatically by the client once a slot migration completes.

Example fix

# before
r.cluster_setslot(target_node, node_id, slot_id, state='STABLE')
# after
r.cluster_setslot_stable(slot_id)
Defensive patterns

Strategy: validation

Validate before calling

if str(state).upper() == 'STABLE':
    # do not call cluster_setslot; route to the dedicated method
    client.cluster_setslot_stable(slot_id)
else:
    client.cluster_setslot(target_node, node_id, slot_id, state=state)

Type guard

def is_directed_state(state: str) -> bool:
    return str(state).upper() in {'IMPORTING', 'NODE', 'MIGRATING'}

Try / catch

from redis.exceptions import RedisError
try:
    client.cluster_setslot(target_node, node_id, slot_id, state=state)
except RedisError as e:
    if 'cluster_setslot_stable' in str(e):
        client.cluster_setslot_stable(slot_id)
    else:
        raise

Prevention

When it happens

Trigger: Calling r.cluster_setslot(target_node, node_id, slot_id, state='STABLE') (any case of 'stable') on a RedisCluster / Redis client. The STABLE branch at redis/commands/cluster.py:887 fires the moment state.upper() == 'STABLE'.

Common situations: Porting a raw 'CLUSTER SETSLOT <slot> STABLE' workflow into the python client verbatim; scripted reshard/drain tooling that loops over all four states uniformly; copying example code that uses the generic server syntax.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/adab4d396c75672e.json. Report an issue: GitHub.