redis/redis-py · error · RuntimeError

pubsub connection not set: did you forget to call…

Error message

pubsub connection not set: did you forget to call subscribe() or psubscribe()?

What it means

Raised by PubSub.parse_response when self.connection is None, meaning the pubsub channel was never opened. The message names the two calls that allocate the connection (subscribe() or psubscribe()). Calling get_message/parse_response before subscribing is the canonical cause.

Solutions

  1. Call await pubsub.subscribe('ch') (or psubscribe('pat*')) before the first get_message/parse_response.
  2. Guard the loop: if pubsub.connection is None, (re)subscribe before reading.
  3. Use pubsub.run(...), which calls connect() for you and only after validating handlers.

Example fix

// before
pubsub = client.pubsub()
msg = await pubsub.get_message(timeout=1.0)
// after
pubsub = client.pubsub()
await pubsub.subscribe('my-channel')
msg = await pubsub.get_message(timeout=1.0)
Defensive patterns

Strategy: validation

Validate before calling

if pubsub.connection is None:
    await pubsub.subscribe('my-channel')
await pubsub.get_message(timeout=1.0)

Type guard

def is_subscribed(pubsub) -> bool:
    return pubsub.connection is not None

Try / catch

try:
    msg = await pubsub.get_message(timeout=1.0)
except RuntimeError as e:
    if 'pubsub connection not set' in str(e):
        await pubsub.subscribe('my-channel')
        msg = await pubsub.get_message(timeout=1.0)
    else:
        raise

Prevention

When it happens

Trigger: Calling pubsub.get_message(...) or pubsub.parse_response(...) on a PubSub object obtained from client.pubsub() without ever invoking subscribe(*channels) or psubscribe(*patterns) on it; or after the connection was torn down (e.g. after an exception that reset pubsub state).

Common situations: Reordering code so the get_message loop starts before subscribe; refactoring that drops the subscribe call; sharing one PubSub object across tasks where one task resets it; calling get_message in a retry loop after a disconnect that nulled the connection.

Related errors


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

Appendix: source

Thrown at redis/asyncio/client.py:1424

            block=False when a timeout is provided, and block=True when timeout=None.

        Example:
            # Block indefinitely (timeout is ignored)
            response = await pubsub.parse_response(block=True, timeout=0.1)

            # Non-blocking with 0.1 second timeout
            response = await pubsub.parse_response(block=False, timeout=0.1)

            # Non-blocking, return immediately
            response = await pubsub.parse_response(block=False, timeout=0)

            # Recommended: use get_message() instead
            msg = await pubsub.get_message(timeout=0.1)  # automatically sets block=False
            msg = await pubsub.get_message(timeout=None)  # automatically sets block=True
        """
        conn = self.connection
        if conn is None:
            raise RuntimeError(
                "pubsub connection not set: "
                "did you forget to call subscribe() or psubscribe()?"
            )

        await self.check_health()

        if not conn.is_connected:
            await conn.connect()

        # Block=True: signal "no timeout" to conn.read_response via
        # math.inf. The connection treats math.inf as the per-read
        # opt-in for blocking indefinitely without falling back to
        # self.socket_timeout. Reconnect/AUTH/HELLO/resubscribe
        # operations performed by the retry layer continue to honor
        # self.socket_timeout because they do not pass math.inf.
        #
        # TODO(next-major): when the async Connection.read_response
        # default for ``timeout`` is changed to SENTINEL, passing

View on GitHub (pinned to 6a6b581b48)