redis/redis-py · error · RedisError

MONITOR failed: {response}

Error message

MONITOR failed: {response}

What it means

Raised by the async Monitor context manager (redis/asyncio/client.py:1092) inside __aenter__. After sending the MONITOR command the library reads the reply and checks it equals the OK status; any other reply means the server did not accept the monitor request. The original reply is interpolated into the message to aid diagnosis.

Source

Thrown at redis/asyncio/client.py:1092

    monitor_re = re.compile(r"\[(\d+) (.*?)\] (.*)")
    command_re = re.compile(r'"(.*?)(?<!\\)"')

    def __init__(self, connection_pool: ConnectionPool):
        self.connection_pool = connection_pool
        self.connection: Optional[Connection] = None

    async def connect(self):
        if self.connection is None:
            self.connection = await self.connection_pool.get_connection()

    async def __aenter__(self):
        await self.connect()
        await self.connection.send_command("MONITOR")
        # check that monitor returns 'OK', but don't return it to user
        response = await self.connection.read_response()
        if not bool_ok(response):
            raise RedisError(f"MONITOR failed: {response}")
        return self

    async def __aexit__(self, *args):
        await self.connection.disconnect()
        await self.connection_pool.release(self.connection)

    async def next_command(self) -> MonitorCommandInfo:
        """Parse the response from a monitor command"""
        await self.connect()
        response = await self.connection.read_response()
        if isinstance(response, bytes):
            response = self.connection.encoder.decode(response, force=True)
        command_time, command_data = response.split(" ", 1)
        m = self.monitor_re.match(command_data)
        db_id, client_info, command = m.groups()
        command = " ".join(self.command_re.findall(command))
        # Redis escapes double quotes because each piece of the command
        # string is surrounded by double quotes. We don't have that

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Use a Redis user/ACL that permits the MONITOR command (admin permission, or the default superuser).
  2. Confirm the target is a real Redis server (not a proxy/cluster node expecting a different command set) and that it has finished loading (INFO persistence loading:0).
  3. Wrap the context-manager entry in try/except RedisError and log response to capture the exact server refusal.
  4. If monitoring a cluster, attach the Monitor to a single node's connection rather than expecting cluster-wide streaming.

Example fix

// before
async with client.monitor() as m:
    async for cmd in m.listen():
        ...
// after
default_user = redis.asyncio.Redis(url="redis://default:adminpw@host:6379")
try:
    async with default_user.monitor() as m:
        async for cmd in m.listen():
            ...
except redis.exceptions.RedisError as e:
    log.error("monitor refused: %s", e)
Defensive patterns

Strategy: try-catch

Try / catch

from redis.exceptions import RedisError
try:
    async with client.monitor() as m:
        async for cmd in m.listen():
            ...
except RedisError as e:
    log.error("MONITOR refused by server: %s", e)

Prevention

When it happens

Trigger: Using `async with client.monitor() as m:` against a server that refuses MONITOR. The server returns a non-OK reply such as an ACL/permission error (NOPERM), BUSYERR when the server is over capacity, or an error on a node that does not allow monitoring. The check is `if not bool_ok(response)`.

Common situations: Running MONITOR with a restricted ACL user that lacks the admin/@slow permission; pointing the client at a replica/proxy that strips MONITOR; hitting a max-clients or loading state where the server returns an error string instead of OK.

Related errors


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