redis/redis-py · error · RuntimeError
pubsub connection not set: did you forget to call subscribe(
Error message
pubsub connection not set: did you forget to call subscribe() or psubscribe()?
What it means
Raised by PubSub.parse_response (redis/asyncio/client.py:1424) when self.connection is None. The pub/sub connection is only allocated by subscribe()/psubscribe()/unsubscribe(); calling parse_response() (or any path that drives reads) before subscribing has no socket to read from, so the library raises a RuntimeError with an explanatory hint.
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 da03cdc7e8)
Solutions
- Call `await pubsub.subscribe(channel)` (or psubscribe) before invoking parse_response/get_message.
- Use get_message(timeout=...) which is the intended public API and is only entered after subscribing.
- Guard the read path: check `pubsub.connection is not None` or `pubsub.subscribed` before reading.
Example fix
// before
pubsub = r.pubsub()
resp = await pubsub.parse_response()
// after
pubsub = r.pubsub()
await pubsub.subscribe("ch")
resp = await pubsub.parse_response() Defensive patterns
Strategy: validation
Validate before calling
pubsub = r.pubsub()
await pubsub.subscribe('ch')
assert pubsub.connection is not None
resp = await pubsub.parse_response() Prevention
- Always call subscribe()/psubscribe() before any read.
- Prefer the higher-level get_message(timeout=...) over parse_response().
- Start worker tasks only after subscription completes.
When it happens
Trigger: Calling `await pubsub.parse_response(...)` or `await pubsub.get_message(...)` on a PubSub object obtained from `client.pubsub()` before any subscribe()/psubscribe() call. The connection field stays None until a subscription allocates a dedicated connection from the pool.
Common situations: A consumer loop started before the subscription call completes; refactoring that reorders subscribe() after the read loop; tests that call parse_response directly to inspect raw frames.
Related errors
- Channel: '{channel}' has no handler registered
- Pattern: '{pattern}' has no handler registered
- Cannot issue nested calls to MULTI
- Commands without an initial WATCH have already been issued
- Cannot issue a WATCH after a MULTI
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/e91ee1f85c5c7166.json.
Report an issue: GitHub.