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
- Verify all startup_nodes belong to the same cluster (same cluster announce / node IDs via redis-cli CLUSTER NODES).
- Retry initialize() after the reshard completes; transient disagreements resolve once ownership stabilises.
- Reduce startup_nodes to the cluster's known-good seed nodes only.
- 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
- Only include startup_nodes from the same cluster.
- Avoid mixing endpoints from different Redis Cluster deployments.
- Retry initialize() transiently during resharding; fix persistent disagreements via CLUSTER NODES.
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
- Slot " " is not covered by the cluster.
- All slots are not covered after query all startup_nodes.
- Failed to subscribe to cluster nodes
- No targets were found to execute
- No targets were found to execute
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 exceptionView on GitHub (pinned to 6a6b581b48)