redis/redis-py · error · ValueError

"max_connections" must be a positive integer

Error message

"max_connections" must be a positive integer

What it means

The async ConnectionPool validates max_connections at construction: max_connections is first coerced via 'or 100' (so 0/None become 100), then if the result is not an int or is negative it raises ValueError. A string, float, bool, or negative number trips it. Note the bound is < 0, so 0 itself does not raise (it becomes 100).

Solutions

  1. Pass an int (typically > 0) for max_connections.
  2. Cast config/env values with int(...) before constructing the pool.
  3. Validate the value's type and sign in your own config loader.

Example fix

# before
pool = ConnectionPool(max_connections=os.environ['MAX_CONN'])
# after
pool = ConnectionPool(max_connections=int(os.environ['MAX_CONN']))
Defensive patterns

Strategy: validation

Validate before calling

if not isinstance(max_connections, int) or isinstance(max_connections, bool) or max_connections < 0:
    raise ValueError('max_connections must be a non-negative int')
max_connections = max_connections or 100

Type guard

def is_valid_max_connections(v) -> bool:
    return isinstance(v, int) and not isinstance(v, bool) and v >= 0

Prevention

When it happens

Trigger: Passing max_connections='10' (string from env/config), max_connections=-1, max_connections=3.5 (float), or max_connections=True/False to ConnectionPool.

Common situations: Reading max_connections from an environment variable or YAML/JSON config as a string without casting; float values from computed config; passing a sentinel that is not an int.

Related errors


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

Appendix: source

Thrown at redis/asyncio/connection.py:2630

        ``ValueError`` to be raised. Once parsed, the querystring arguments
        and keyword arguments are passed to the ``ConnectionPool``'s
        class initializer. In the case of conflicting arguments, querystring
        arguments always win.
        """
        url_options = parse_url(url)
        kwargs.update(url_options)
        return cls(**kwargs)

    def __init__(
        self,
        connection_class: Type[AbstractConnection] = Connection,
        max_connections: Optional[int] = None,
        maint_notifications_config: MaintNotificationsConfig | None = None,
        **connection_kwargs,
    ):
        max_connections = max_connections or 100
        if not isinstance(max_connections, int) or max_connections < 0:
            raise ValueError('"max_connections" must be a positive integer')

        self.connection_class = connection_class
        self._connection_kwargs = connection_kwargs
        self.max_connections = max_connections

        # Resolve the HIMPORT registry. A pre-built ``himport_registry`` (shared, e.g.
        # from the cluster client) takes precedence; otherwise build a fresh empty one.
        # A registry always exists so runtime ``himport_prepare`` mutates a single object
        # every connection already shares. The object stays in ``connection_kwargs`` so
        # it reaches every connection. It is injected unconditionally (like other
        # auto-added pool kwargs), so a custom ``connection_class`` must accept
        # ``**kwargs`` (or a ``himport_registry`` parameter), as built-ins do.
        himport_registry = connection_kwargs.get("himport_registry")
        if himport_registry is None:
            himport_registry = HImportRegistry()
            connection_kwargs["himport_registry"] = himport_registry
        self.himport_registry = himport_registry

View on GitHub (pinned to 6a6b581b48)