{"id":"c5ca167f28ea36cd","repo":"redis/redis-py","slug":"failed-to-subscribe-to-cluster-nodes-join-f","errorCode":null,"errorMessage":"Failed to subscribe to cluster nodes: {', '.join(failed_nodes)}","messagePattern":"Failed to subscribe to cluster nodes: (.+?)","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"warning","filePath":"redis/asyncio/keyspace_notifications.py","lineNumber":1019,"sourceCode":"            new_nodes = set(current_primaries.keys()) - set(self._node_pubsubs.keys())\n            failed_nodes: list[str] = []\n            for node_name in new_nodes:\n                node = current_primaries[node_name]\n                pubsub = await self._ensure_node_pubsub(node)\n\n                try:\n                    if self._subscribed_patterns:\n                        await pubsub.psubscribe(**self._subscribed_patterns)\n                    if self._subscribed_channels:\n                        await pubsub.subscribe(**self._subscribed_channels)\n                except Exception:\n                    # Subscription failed - remove from dict so retry is possible\n                    await self._cleanup_node(node_name)\n                    failed_nodes.append(node_name)\n\n            # Raise after attempting all nodes so we don't skip any\n            if failed_nodes:\n                raise ConnectionError(\n                    f\"Failed to subscribe to cluster nodes: {', '.join(failed_nodes)}\"\n                )\n\n    async def aclose(self):\n        \"\"\"Close all pubsub connections and clean up resources.\"\"\"\n        self._closed = True\n        for node_name in list(self._node_pubsubs.keys()):\n            await self._cleanup_node(node_name)\n        self._subscribed_patterns.clear()\n        self._subscribed_channels.clear()\n","sourceCodeStart":1001,"sourceCodeEnd":1030,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/keyspace_notifications.py#L1001-L1030","documentation":"Raised as ConnectionError by ClusterKeyspaceNotifications.refresh_subscriptions() when one or more newly discovered primary nodes could not be subscribed to. The method attempts every new node, collects failures, then raises once with the list of failed node names. Existing subscriptions and cleanup of failed nodes still happen; the raise signals that the topology is not fully subscribed.","triggerScenarios":"Cluster topology change (failover/scale-out) adds new primary nodes; refresh_subscriptions tries to psubscribe/subscribe the new nodes' pubsub connections and the subscribe call raises (network error, node unreachable, auth failure). All failing node names are aggregated into the message.","commonSituations":"Cluster failover in progress where a new primary is briefly unreachable; network partition; auth/ACL mismatch on a new primary; partial outage during scaling; calling refresh_subscriptions manually right after a topology change.","solutions":["Retry refresh_subscriptions after a short backoff; the failed nodes are cleaned up so a later attempt re-subscribes them.","Verify network reachability and ACL/credentials for the listed nodes.","Ensure the cluster topology has stabilized (CLUSTER NODES) before retrying.","If recurring, check the pubsub connection limits and per-node connection counts."],"exampleFix":"// before\nawait notifier.refresh_subscriptions()  # raises on first transient failure\n// after\nfor attempt in range(5):\n    try:\n        await notifier.refresh_subscriptions()\n        break\n    except ConnectionError:\n        await asyncio.sleep(2 ** attempt)\\nelse:\\n    raise","handlingStrategy":"retry","validationCode":"async def safe_refresh(notifier, attempts=5):\n    for i in range(attempts):\n        try:\n            await notifier.refresh_subscriptions()\n            return True\n        except ConnectionError:\n            await asyncio.sleep(2 ** i)\n    return False","typeGuard":"def cluster_topology_stable(cluster) -> bool:\n    # heuristically, all slots assigned and no FAIL/HANDSHAKE nodes\n    nodes = cluster.cluster_nodes()\n    return all(n['flags'] and 'fail' not in n['flags'] for n in nodes)","tryCatchPattern":"from redis.exceptions import ConnectionError\ntry:\n    await notifier.refresh_subscriptions()\nexcept ConnectionError as e:\n    if 'Failed to subscribe' in str(e):\n        await asyncio.sleep(backoff)\n        await notifier.refresh_subscriptions()","preventionTips":["Wrap refresh_subscriptions in a retried loop during failovers.","Verify reachability/ACLs on all primaries before subscribing.","Let topology stabilize (CLUSTER NODES) before retrying."],"tags":["cluster","keyspace-notifications","topology","retry","asyncio"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}