{"id":"2f35cc87756dfb37","repo":"redis/redis-py","slug":"startup-nodes-could-not-agree-on-a-valid-slots-cac","errorCode":null,"errorMessage":"startup_nodes could not agree on a valid slots cache: {', '.join(disagreements)}","messagePattern":"startup_nodes could not agree on a valid slots cache: (.+?)","errorType":"exception","errorClass":"RedisClusterException","httpStatus":null,"severity":"critical","filePath":"redis/asyncio/cluster.py","lineNumber":2356,"sourceCode":"                            )\n                        # add this node to the nodes cache\n                        tmp_nodes_cache[target_replica_node.name] = target_replica_node\n                        nodes_for_slot.append(target_replica_node)\n\n                    for i in range(int(slot[0]), int(slot[1]) + 1):\n                        if i not in tmp_slots:\n                            tmp_slots[i] = nodes_for_slot\n                        else:\n                            # Validate that 2 nodes want to use the same slot cache\n                            # setup\n                            tmp_slot = tmp_slots[i][0]\n                            if tmp_slot.name != target_node.name:\n                                disagreements.append(\n                                    f\"{tmp_slot.name} vs {target_node.name} on slot: {i}\"\n                                )\n\n                                if len(disagreements) > 5:\n                                    raise RedisClusterException(\n                                        f\"startup_nodes could not agree on a valid \"\n                                        f\"slots cache: {', '.join(disagreements)}\"\n                                    )\n\n                # Validate if all slots are covered or if we should try next startup node\n                fully_covered = True\n                for i in range(REDIS_CLUSTER_HASH_SLOTS):\n                    if i not in tmp_slots:\n                        fully_covered = False\n                        break\n                if fully_covered:\n                    break\n\n            if not startup_nodes_reachable:\n                raise RedisClusterException(\n                    f\"Redis Cluster cannot be connected. Please provide at least \"\n                    f\"one reachable node: {str(exception)}\"\n                ) from exception","sourceCodeStart":2338,"sourceCodeEnd":2374,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L2338-L2374","documentation":"Raised during initialize() when more than 5 slot-ownership disagreements accumulate across startup nodes. As the client merges CLUSTER SLOTS replies from multiple nodes, if two different nodes claim ownership of the same slot (line 2350 check), the disagreement is recorded; exceeding the threshold aborts bootstrapping because the topology is inconsistent and cannot be safely resolved.","triggerScenarios":"Bootstrapping against a cluster mid-reshard where slots are being migrated and nodes disagree transiently; connecting during an unstable split-brain; nodes running mismatched cluster configs; a partially-failed failover leaving conflicting slot claims.","commonSituations":"Connecting during active slot migration/rebalancing; a flaky network partition where nodes' CLUSTER SLOTS are out of sync; multiple startup_nodes pointing at different sub-clusters.","solutions":["Retry client creation after resharding/failover completes (await asyncio.sleep then reconstruct RedisCluster).","Reduce startup_nodes to a single known-good seed node so only one CLUSTER SLOTS view is trusted.","Ensure the cluster is healthy (CLUSTER NODES shows no fail/pfail state) before the client connects."],"exampleFix":"// before\nrc = RedisCluster(startup_nodes=[nodeA, nodeB, nodeC])  # during reshard\n\n// after\n# wait for reshard to finish, then seed from one node\nrc = RedisCluster(host='seed-host', port=6379)","handlingStrategy":"retry","validationCode":"import asyncio\nfrom redis.asyncio.cluster import RedisCluster\n\nasync def connect_stable(startup_nodes, max_retries=3):\n    for attempt in range(max_retries):\n        try:\n            return await RedisCluster(startup_nodes=startup_nodes)\n        except Exception as e:\n            if 'could not agree' in str(e):\n                await asyncio.sleep(2 ** attempt)\n                continue\n            raise\n    raise RuntimeError('cluster topology still unstable')","typeGuard":"def cluster_looks_healthy(cluster_nodes_text: str) -> bool:\n    lines = [l for l in cluster_nodes_text.splitlines() if l and not l.startswith('node')]\n    return all('fail' not in l.split()[-2].lower() for l in lines if len(l.split()) >= 8)","tryCatchPattern":"from redis.cluster import RedisClusterException\n\ntry:\n    rc = RedisCluster(startup_nodes=seeds)\nexcept RedisClusterException as e:\n    if 'could not agree' in str(e):\n        # wait for reshard/failover to settle and seed from one node\n        await asyncio.sleep(5)\n        rc = RedisCluster(host=seeds[0].host, port=seeds[0].port)\n    else:\n        raise","preventionTips":["Avoid constructing the client during active resharding or failover.","Seed from a single known-good node rather than many divergent seeds.","Verify cluster_state:ok via CLUSTER INFO before connecting."],"tags":["redis-cluster","startup","topology","split-brain","resharding"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}