{"record":{"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":787,"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":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/cluster.py#L769-L805","documentation":"Raised in RedisCluster.on_connect after sending READONLY and reading the reply: if the reply is not the string 'OK' the connection is considered misconfigured for replica reads and ConnectionError is raised. on_connect runs for every new connection when read_from_replicas or load_balancing_strategy is set, so this surfaces at first use of each connection.","triggerScenarios":"Constructing RedisCluster(..., read_from_replicas=True) (or load_balancing_strategy=...) and connecting to a node that rejects READONLY — e.g. the node is a primary that disallows READONLY, the ACL user lacks permission, or the server is a proxy that returns an unexpected reply.","commonSituations":"Pointing read_from_replicas=True at a non-cluster or single-node setup where READONLY is meaningless; ACL restrictions on the connecting user; a managed Redis that does not support READONLY.","solutions":["Only enable read_from_replicas/load_balancing_strategy against a real cluster with replicas.","Grant the connecting ACL user permission to issue READONLY.","If you do not need replica reads, leave read_from_replicas=False (default)."],"exampleFix":"// before\nclient = RedisCluster(host=..., port=..., read_from_replicas=True)\n// after\n# only enable if the topology has replicas and the ACL permits READONLY\nclient = RedisCluster(host=..., port=..., read_from_replicas=False)\n# or, with replicas:\nclient = RedisCluster(host=..., port=..., read_from_replicas=True)  # and fix ACL","handlingStrategy":"validation","validationCode":"read_from_replicas = read_from_replicas and bool(client.get_replicas())\n# Or statically: only enable read_from_replicas for known-replica topologies.","typeGuard":"def cluster_supports_readonly(client) -> bool:\n    try:\n        return len(client.get_replicas()) > 0\n    except Exception:\n        return False","tryCatchPattern":"from redis.exceptions import ConnectionError\ntry:\n    await client.set('k', 'v')\nexcept ConnectionError as e:\n    if 'READONLY command failed' in str(e):\n        client = RedisCluster(host=..., port=..., read_from_replicas=False)\n        await client.set('k', 'v')\n    else:\n        raise","preventionTips":["Enable read_from_replicas only against clusters that have replicas.","Grant the ACL user permission to issue READONLY."],"tags":["cluster","replicas","readonly","acl","connection"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}