{"id":"ecc974bc2b28ca40","repo":"redis/redis-py","slug":"readonly-command-failed","errorCode":null,"errorMessage":"READONLY command failed","messagePattern":"READONLY command failed","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/cluster.py","lineNumber":786,"sourceCode":"        if hasattr(self, \"_initialize\") and not self._initialize:\n            _warn(f\"{self._DEL_MESSAGE} {self!r}\", ResourceWarning, source=self)\n            try:\n                context = {\"client\": self, \"message\": self._DEL_MESSAGE}\n                _grl().call_exception_handler(context)\n            except RuntimeError:\n                pass\n\n    async def on_connect(self, connection: Connection) -> None:\n        await connection.on_connect()\n\n        # Sending READONLY command to server to configure connection as\n        # readonly. Since each cluster node may change its server type due\n        # to a failover, we should establish a READONLY connection\n        # regardless of the server type. If this is a primary connection,\n        # READONLY would not affect executing write commands.\n        await connection.send_command(\"READONLY\")\n        if str_if_bytes(await connection.read_response()) != \"OK\":\n            raise ConnectionError(\"READONLY command failed\")\n\n    def get_nodes(self) -> List[\"ClusterNode\"]:\n        \"\"\"Get all nodes of the cluster.\"\"\"\n        return list(self.nodes_manager.nodes_cache.values())\n\n    def get_primaries(self) -> List[\"ClusterNode\"]:\n        \"\"\"Get the primary nodes of the cluster.\"\"\"\n        return self.nodes_manager.get_nodes_by_server_type(PRIMARY)\n\n    def get_replicas(self) -> List[\"ClusterNode\"]:\n        \"\"\"Get the replica nodes of the cluster.\"\"\"\n        return self.nodes_manager.get_nodes_by_server_type(REPLICA)\n\n    def get_random_node(self) -> \"ClusterNode\":\n        \"\"\"Get a random node of the cluster.\"\"\"\n        return random.choice(list(self.nodes_manager.nodes_cache.values()))\n\n    def get_default_node(self) -> \"ClusterNode\":","sourceCodeStart":768,"sourceCodeEnd":804,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L768-L804","documentation":"Raised by RedisCluster.on_connect (redis/asyncio/cluster.py:786) when the READONLY command reply is not 'OK'. When read_from_replicas or load_balancing_strategy is set, each new connection sends READONLY so replica connections accept reads. If the server refuses (returns non-OK), the connection setup fails with ConnectionError, which the pool treats as a connection failure.","triggerScenarios":"Connecting read_from_replicas=True to an endpoint that rejects READONLY: a standalone (non-cluster) Redis, a node where the connection landed on a primary that disallows READONLY under certain configs, or an ACL/permission denial. on_connect runs after the standard handshake.","commonSituations":"Pointing a cluster client at a non-cluster Redis; ACL user lacking permission; proxy that does not implement READONLY; connecting before the node has joined the cluster.","solutions":["Ensure the target is a real Redis Cluster node (run CLUSTER INFO).","If you do not need replica reads, drop read_from_replicas/load_balancing_strategy so on_connect skips READONLY.","Verify the ACL user can issue READONLY and that replicas are reachable.","Retry: this raises ConnectionError so the retry layer will attempt reconnects if configured."],"exampleFix":"// before\nc = RedisCluster(host='localhost', port=16379, read_from_replicas=True)\n// (against a standalone Redis)\n// after\nc = redis.asyncio.Redis(host='localhost', port=6379)  # standalone, no READONLY\n# or, for a real cluster, keep read_from_replicas=True and verify replicas exist","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import ConnectionError\ntry:\n    await c.get('k')\nexcept ConnectionError as e:\n    if 'READONLY' in str(e):\n        log.error('target may not be a cluster node or ACL denies READONLY')\n    raise","preventionTips":["Confirm the target is a Redis Cluster node (CLUSTER INFO).","Drop read_from_replicas unless you actually have replicas and need them.","Ensure the ACL user can issue READONLY."],"tags":["redis","asyncio","cluster","connection","replica-read","readonly"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}