{"record":{"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":"error","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/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/keyspace_notifications.py#L1001-L1030","documentation":"In cluster keyspace-notification mode, refresh_subscriptions discovers current primaries and (p)subscribes each new/broken one. Any node whose psubscribe/subscribe throws is recorded; after all nodes are attempted a single ConnectionError is raised listing the failed node names. Successful nodes are kept, so the failure is partial. refresh_subscriptions runs automatically on topology change or connection errors during get_message().","triggerScenarios":"A cluster failover or topology refresh where some primaries are unreachable or reject SUBSCRIBE/PSUBSCRIBE; triggered from get_message() error recovery or a manual refresh_subscriptions() call.","commonSituations":"Cluster rolling restart; transient network partition; ACLs on some nodes disallowing SUBSCRIBE; nodes temporarily down during a scale event.","solutions":["Retry: refresh_subscriptions re-runs automatically on the next get_message() error.","Check reachability and health of the named nodes.","Verify ACLs permit SUBSCRIBE/PSUBSCRIBE on every primary.","Let cluster topology stabilize (avoid mid-failover subscription storms)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import ConnectionError\nfor attempt in range(retries):\n    try:\n        return await pubsub.get_message(timeout=1)\n    except ConnectionError as e:\n        if 'Failed to subscribe to cluster nodes' not in str(e):\n            raise\n        await asyncio.sleep(backoff(attempt))\nraise","preventionTips":["Keep cluster topology stable during heavy pubsub use.","Ensure ACLs on every primary permit SUBSCRIBE/PSUBSCRIBE.","Rely on refresh_subscriptions auto-retry from get_message()."],"tags":["cluster","keyspace-notifications","pubsub","transient","async"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}