redis/redis-py · error · ConnectionError
READONLY command failed
Error message
READONLY command failed
What it means
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.
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).
Example fix
// before client = RedisCluster(host=..., port=..., read_from_replicas=True) // after # only enable if the topology has replicas and the ACL permits READONLY client = RedisCluster(host=..., port=..., read_from_replicas=False) # or, with replicas: client = RedisCluster(host=..., port=..., read_from_replicas=True) # and fix ACL
Defensive patterns
Strategy: validation
Validate before calling
read_from_replicas = read_from_replicas and bool(client.get_replicas()) # Or statically: only enable read_from_replicas for known-replica topologies.
Type guard
def cluster_supports_readonly(client) -> bool:
try:
return len(client.get_replicas()) > 0
except Exception:
return False Try / catch
from redis.exceptions import ConnectionError
try:
await client.set('k', 'v')
except ConnectionError as e:
if 'READONLY command failed' in str(e):
client = RedisCluster(host=..., port=..., read_from_replicas=False)
await client.set('k', 'v')
else:
raise Prevention
- Enable read_from_replicas only against clusters that have replicas.
- Grant the ACL user permission to issue READONLY.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cluster Node has no redis_connection
- Error setting client name
- Failed to subscribe to cluster nodes
- Invalid Database
- READONLY command failed
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/ecc974bc2b28ca40.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/cluster.py:787
if hasattr(self, "_initialize") and not self._initialize:
_warn(f"{self._DEL_MESSAGE} {self!r}", ResourceWarning, source=self)
try:
context = {"client": self, "message": self._DEL_MESSAGE}
_grl().call_exception_handler(context)
except RuntimeError:
pass
async def on_connect(self, connection: Connection) -> None:
await connection.on_connect()
# Sending READONLY command to server to configure connection as
# readonly. Since each cluster node may change its server type due
# to a failover, we should establish a READONLY connection
# regardless of the server type. If this is a primary connection,
# READONLY would not affect executing write commands.
await connection.send_command("READONLY")
if str_if_bytes(await connection.read_response()) != "OK":
raise ConnectionError("READONLY command failed")
def get_nodes(self) -> List["ClusterNode"]:
"""Get all nodes of the cluster."""
return list(self.nodes_manager.nodes_cache.values())
def get_primaries(self) -> List["ClusterNode"]:
"""Get the primary nodes of the cluster."""
return self.nodes_manager.get_nodes_by_server_type(PRIMARY)
def get_replicas(self) -> List["ClusterNode"]:
"""Get the replica nodes of the cluster."""
return self.nodes_manager.get_nodes_by_server_type(REPLICA)
def get_random_node(self) -> "ClusterNode":
"""Get a random node of the cluster."""
return random.choice(list(self.nodes_manager.nodes_cache.values()))
def get_default_node(self) -> "ClusterNode":View on GitHub (pinned to 6a6b581b48)