redis/redis-py · error · ConnectionError

protocol must be an integer

Error message

protocol must be an integer

What it means

Raised as ConnectionError in Connection.__init__ when int(protocol) raises ValueError — i.e. protocol was a non-empty string that isn't a base-10 integer (e.g. protocol='3.0', protocol='resp3', protocol=True-ish strings). Note: a None or non-coercible type falls through to DEFAULT_RESP_VERSION (TypeError branch), so this specifically fires on string-but-not-int input.

Source

Thrown at redis/asyncio/connection.py:701

        self.health_check_interval = health_check_interval
        self.next_health_check: float = -1
        self.encoder = encoder_class(encoding, encoding_errors, decode_responses)
        self.redis_connect_func = redis_connect_func
        self._reader: Optional[asyncio.StreamReader] = None
        self._writer: Optional[asyncio.StreamWriter] = None
        self._socket_read_size = socket_read_size
        self._active_read_timeout = None
        self._connect_callbacks: List[weakref.WeakMethod[ConnectCallbackT]] = []
        self._buffer_cutoff = 6000
        self._re_auth_token: Optional[TokenInterface] = None
        self._should_reconnect = False

        try:
            p = int(protocol)
        except TypeError:
            p = DEFAULT_RESP_VERSION
        except ValueError:
            raise ConnectionError("protocol must be an integer")
        else:
            if p < 2 or p > 3:
                raise ConnectionError("protocol must be either 2 or 3")
        self.protocol = p
        self.legacy_responses = legacy_responses
        if parser_class != _AsyncHiredisParser:
            # The Python parsers are protocol-specific; hiredis supports both.
            if self.protocol == 3 and parser_class == _AsyncRESP2Parser:
                parser_class = _AsyncRESP3Parser
            elif self.protocol == 2 and parser_class == _AsyncRESP3Parser:
                parser_class = _AsyncRESP2Parser
        self.set_parser(parser_class)

        # HIMPORT client-side state. `himport_registry` is the shared client-level
        # registry (empty if unconfigured) and persists across reconnects.
        self.himport_registry = himport_registry
        self._reset_himport_state()

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Pass an integer literal: protocol=3 or protocol=2.
  2. Cast explicitly when reading from config: protocol=int(os.environ['REDIS_PROTOCOL']).
  3. Omit the argument entirely to use the library default.

Example fix

// before
client = Redis(url='...', protocol=os.environ['REDIS_PROTO'])  # '3' ok, '3.0' raises [95]
// after
client = Redis(url='..., protocol=int(os.environ['REDIS_PROTO']))
Defensive patterns

Strategy: validation

Validate before calling

try:
    protocol = int(protocol)
except (TypeError, ValueError):
    raise ValueError(f'protocol must be an integer, got {protocol!r}')

Try / catch

from redis.exceptions import ConnectionError
try:
    client = Redis(protocol=protocol_str)
except ConnectionError as e:
    if 'protocol must be an integer' in str(e):
        client = Redis(protocol=int(protocol_str))

Prevention

When it happens

Trigger: Passing protocol='3.0', protocol='three', protocol='2.5', or any string that fails int() parsing to Redis()/Connection().

Common situations: Reading protocol from an env var or YAML as a string and forgetting to cast; URL query parsing that yields 'protocol=3.0'.

Related errors


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