redis/redis-py · error · RedisClusterException

startup_nodes could not agree on a valid slots cache

Error message

startup_nodes could not agree on a valid slots cache: {', '.join(disagreements)}

What it means

Raised while building tmp_slots during initialize() when more than 5 slot-range disagreements are detected between the responses of different startup nodes (two nodes claim ownership of the same slot for different primaries). This indicates conflicting cluster views, so the client aborts rather than picking one arbitrarily.

Solutions

  1. Verify all startup_nodes belong to the same cluster (same cluster announce / node IDs via redis-cli CLUSTER NODES).
  2. Retry initialize() after the reshard completes; transient disagreements resolve once ownership stabilises.
  3. Reduce startup_nodes to the cluster's known-good seed nodes only.
  4. If persistent, inspect CLUSTER NODES on each node to find the disagreement and fix the broken slot assignment.

Example fix

// before
rc = RedisCluster(startup_nodes=[
    ClusterNode('cluster-a', 7000),
    ClusterNode('cluster-b', 7000),  # different cluster!
])

// after
rc = RedisCluster(startup_nodes=[
    ClusterNode('cluster-a', 7000),
    ClusterNode('cluster-a', 7001),
])
Defensive patterns

Strategy: validation

Validate before calling

async def assert_same_cluster(startup_nodes):
    # compare cluster node ids across all seed nodes
    from redis.asyncio import Redis
    node_ids = set()
    for host, port in startup_nodes:
        r = Redis(host=host, port=port)
        try:
            nid = (await r.execute_command('CLUSTER', 'MYID'))
            node_ids.add(nid)
        finally:
            await r.aclose()
    # all seeds should reference one cluster; not identical ids but overlapping membership
    return node_ids

Type guard

null

Try / catch

from redis.exceptions import RedisClusterException
try:
    await rc.initialize()
except RedisClusterException as e:
    if 'could not agree' in str(e):
        # trim startup_nodes to known-good seeds and retry
        rc.startup_nodes = {n.name: n for n in rc.startup_nodes.values()[:1]}
        await rc.initialize()

Prevention

When it happens

Trigger: Two or more startup nodes disagree on which node owns a slot, accumulating >5 conflicts before the full-coverage check. Typical during a botched reshard, a split-brain, or when startup_nodes inadvertently mix nodes from two different clusters.

Common situations: Mixing endpoints from different Redis Cluster deployments in startup_nodes; mid-reshard race where slot ownership is being transferred; nodes running incompatible cluster configs.

Related errors


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

Appendix: source

Thrown at redis/asyncio/cluster.py:2357

                            )
                        # add this node to the nodes cache
                        tmp_nodes_cache[target_replica_node.name] = target_replica_node
                        nodes_for_slot.append(target_replica_node)

                    for i in range(int(slot[0]), int(slot[1]) + 1):
                        if i not in tmp_slots:
                            tmp_slots[i] = nodes_for_slot
                        else:
                            # Validate that 2 nodes want to use the same slot cache
                            # setup
                            tmp_slot = tmp_slots[i][0]
                            if tmp_slot.name != target_node.name:
                                disagreements.append(
                                    f"{tmp_slot.name} vs {target_node.name} on slot: {i}"
                                )

                                if len(disagreements) > 5:
                                    raise RedisClusterException(
                                        f"startup_nodes could not agree on a valid "
                                        f"slots cache: {', '.join(disagreements)}"
                                    )

                # Validate if all slots are covered or if we should try next startup node
                fully_covered = True
                for i in range(REDIS_CLUSTER_HASH_SLOTS):
                    if i not in tmp_slots:
                        fully_covered = False
                        break
                if fully_covered:
                    break

            if not startup_nodes_reachable:
                raise RedisClusterException(
                    f"Redis Cluster cannot be connected. Please provide at least "
                    f"one reachable node: {str(exception)}"
                ) from exception

View on GitHub (pinned to 6a6b581b48)