redis/redis-py · error · RedisError

Maintenance notifications handlers on connection are only…

Error message

Maintenance notifications handlers on connection are only supported with RESP version 3

What it means

Raised when an async Redis client is constructed with a maint_notifications_config whose enabled flag is True while the negotiated wire protocol is not RESP3 (protocol != 3). Maintenance notifications are pushed by the server only via the RESP3 push type, so the library refuses to start a client that could never receive them. The guard lives in Redis.__init__ after the connection kwargs are assembled, before the connection pool is created.

Solutions

  1. Set protocol=3 on the same Redis() call that passes maint_notifications_config.
  2. If you must stay on RESP2, pass maint_notifications_config=None (or enabled=False).
  3. Confirm the server is Redis >= 7.0 (maintenance notifications require RESP3 support).

Example fix

// before
client = redis.asyncio.Redis(
    maint_notifications_config=MaintNotificationsConfig(enabled=True),
    protocol=2,
)
// after
client = redis.asyncio.Redis(
    maint_notifications_config=MaintNotificationsConfig(enabled=True),
    protocol=3,
)
Defensive patterns

Strategy: validation

Validate before calling

from redis._parsers.helpers import check_protocol_version
if maint_cfg and maint_cfg.enabled and not check_protocol_version(protocol, 3):
    raise ValueError('enable RESP3 (protocol=3) before maint notifications')

Type guard

def can_use_maint_notifications(protocol: int | None) -> bool:
    return check_protocol_version(protocol, 3)

Try / catch

try:
    client = redis.asyncio.Redis(maint_notifications_config=cfg, protocol=protocol)
except RedisError as e:
    if 'RESP version 3' in str(e):
        client = redis.asyncio.Redis(maint_notifications_config=None, protocol=protocol)
    else:
        raise

Prevention

When it happens

Trigger: Constructing redis.asyncio.Redis(maint_notifications_config=MaintNotificationsConfig(enabled=True), protocol=2) (or omitting protocol while the connection default is RESP2), or passing a config object built with enabled=True to from_url without forcing protocol=3.

Common situations: Copying a maintenance-notification example into an app whose other clients still use protocol=2; upgrading redis-py to enable maintenance notifications on a legacy RESP2 deployment; setting the env var REDIS_PROTOCOL=2 globally and then enabling notifications on one client.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/9fb6abfa31a0587e. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/client.py:469

                            "ssl_keyfile": ssl_keyfile,
                            "ssl_certfile": ssl_certfile,
                            "ssl_cert_reqs": ssl_cert_reqs,
                            "ssl_include_verify_flags": ssl_include_verify_flags,
                            "ssl_exclude_verify_flags": ssl_exclude_verify_flags,
                            "ssl_ca_certs": ssl_ca_certs,
                            "ssl_ca_data": ssl_ca_data,
                            "ssl_ca_path": ssl_ca_path,
                            "ssl_check_hostname": ssl_check_hostname,
                            "ssl_min_version": ssl_min_version,
                            "ssl_ciphers": ssl_ciphers,
                            "ssl_password": ssl_password,
                        }
                    )
            maint_notifications_enabled = (
                maint_notifications_config and maint_notifications_config.enabled
            )
            if maint_notifications_enabled and not check_protocol_version(protocol, 3):
                raise RedisError(
                    "Maintenance notifications handlers on connection are only supported with RESP version 3"
                )
            if maint_notifications_config:
                kwargs.update(
                    {
                        "maint_notifications_config": maint_notifications_config,
                    }
                )
            # This arg only used if no pool is passed in
            self.auto_close_connection_pool = auto_close_connection_pool
            connection_pool = ConnectionPool(**kwargs)
            self._event_dispatcher.dispatch(
                AfterPooledConnectionsInstantiationEvent(
                    [connection_pool], ClientType.ASYNC, credential_provider
                )
            )
        else:
            # If a pool is passed in, do not close it

View on GitHub (pinned to 6a6b581b48)