redis/redis-py · error · RedisError

Invalid slot state

Error message

Invalid slot state: {state}

What it means

Raised by RedisCluster.cluster_setslot() when the `state` argument is not one of the accepted values. The method only accepts 'IMPORTING', 'NODE', 'MIGRATING' (which it forwards to CLUSTER SETSLOT) and treats 'STABLE' specially by redirecting to cluster_setslot_stable(). Any other value is rejected client-side before any command is sent to Redis. This is an input-validation guard, not a server error.

Solutions

  1. Use one of 'IMPORTING', 'NODE', or 'MIGRATING' as the state argument.
  2. If you meant to clear a slot's migration state, call cluster_setslot_stable(slot_id) instead of cluster_setslot(..., state='STABLE').
  3. Check the spelling and case of the state string against the accepted set.

Example fix

# before
client.cluster_setslot(node, node_id, slot_id, state='MIGRATE')
# after
client.cluster_setslot(node, node_id, slot_id, state='MIGRATING')
# or for clearing state:
client.cluster_setslot_stable(slot_id)
Defensive patterns

Strategy: validation

Validate before calling

VALID_SLOT_STATES = {'IMPORTING', 'NODE', 'MIGRATING'}
def set_slot(node, node_id, slot_id, state):
    s = state.upper()
    if s == 'STABLE':
        client.cluster_setslot_stable(slot_id)
    elif s in VALID_SLOT_STATES:
        client.cluster_setslot(node, node_id, slot_id, state)
    else:
        raise ValueError(f'state must be one of {VALID_SLOT_STATES | {"STABLE"}}, got {state!r}')

Type guard

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

Try / catch

from redis.exceptions import RedisError
try:
    client.cluster_setslot(node, node_id, slot_id, state)
except RedisError as e:
    if 'Invalid slot state' in str(e):
        # fix state and retry or surface config error
        ...
    raise

Prevention

When it happens

Trigger: Calling client.cluster_setslot(target_node, node_id, slot_id, state) with a `state` string outside {'IMPORTING','NODE','MIGRATING','STABLE'} (case-insensitive). Examples: state='MIGRATE' (typo), state='MOVING', state='IMPORT', state='', or passing an int/None as state (AttributeError on .upper() aside, the branch falls through to the else).

Common situations: Migrating hash slots between cluster nodes manually; misspelling a slot state constant; using a value valid in redis-cli but not exposed by this client wrapper; copy-pasting from Redis docs that list states the client intentionally funnels through a separate method.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/bc47f76a390fedd6. Report an issue: GitHub.

Appendix: source

Thrown at redis/commands/cluster.py:890

    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
        """
        return self.execute_command("CLUSTER SETSLOT", slot_id, "STABLE")

View on GitHub (pinned to 6a6b581b48)