redis/redis-py · error · ConnectionError

READONLY command failed

Error message

READONLY command failed

What it means

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.

Source

Thrown at redis/asyncio/cluster.py:786

        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 da03cdc7e8)

Solutions

  1. Ensure the target is a real Redis Cluster node (run CLUSTER INFO).
  2. If you do not need replica reads, drop read_from_replicas/load_balancing_strategy so on_connect skips READONLY.
  3. Verify the ACL user can issue READONLY and that replicas are reachable.
  4. Retry: this raises ConnectionError so the retry layer will attempt reconnects if configured.

Example fix

// before
c = RedisCluster(host='localhost', port=16379, read_from_replicas=True)
// (against a standalone Redis)
// after
c = redis.asyncio.Redis(host='localhost', port=6379)  # standalone, no READONLY
# or, for a real cluster, keep read_from_replicas=True and verify replicas exist
Defensive patterns

Strategy: try-catch

Try / catch

from redis.exceptions import ConnectionError
try:
    await c.get('k')
except ConnectionError as e:
    if 'READONLY' in str(e):
        log.error('target may not be a cluster node or ACL denies READONLY')
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/ecc974bc2b28ca40.json. Report an issue: GitHub.