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
- Call await pubsub.subscribe('ch') (or psubscribe('pat*')) before the first get_message/parse_response.
- Guard the loop: if pubsub.connection is None, (re)subscribe before reading.
- 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
- Centralize pubsub usage in a helper that always subscribes before reading.
- Prefer pubsub.run(...) which connects for you.
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
- Buffer is closed.
- A non health check response was cleaned by execute_command
- Connection closed by server.
- pubsub connection not set: did you forget to call…
- A occurred while watching one or more keys
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, passingView on GitHub (pinned to 6a6b581b48)