{"record":{"id":"e91ee1f85c5c7166","repo":"redis/redis-py","slug":"pubsub-connection-not-set-did-you-forget-to-call","errorCode":null,"errorMessage":"pubsub connection not set: did you forget to call subscribe() or psubscribe()?","messagePattern":"pubsub connection not set: did you forget to call subscribe\\(\\) or psubscribe\\(\\)\\?","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/client.py","lineNumber":1424,"sourceCode":"            block=False when a timeout is provided, and block=True when timeout=None.\n\n        Example:\n            # Block indefinitely (timeout is ignored)\n            response = await pubsub.parse_response(block=True, timeout=0.1)\n\n            # Non-blocking with 0.1 second timeout\n            response = await pubsub.parse_response(block=False, timeout=0.1)\n\n            # Non-blocking, return immediately\n            response = await pubsub.parse_response(block=False, timeout=0)\n\n            # Recommended: use get_message() instead\n            msg = await pubsub.get_message(timeout=0.1)  # automatically sets block=False\n            msg = await pubsub.get_message(timeout=None)  # automatically sets block=True\n        \"\"\"\n        conn = self.connection\n        if conn is None:\n            raise RuntimeError(\n                \"pubsub connection not set: \"\n                \"did you forget to call subscribe() or psubscribe()?\"\n            )\n\n        await self.check_health()\n\n        if not conn.is_connected:\n            await conn.connect()\n\n        # Block=True: signal \"no timeout\" to conn.read_response via\n        # math.inf. The connection treats math.inf as the per-read\n        # opt-in for blocking indefinitely without falling back to\n        # self.socket_timeout. Reconnect/AUTH/HELLO/resubscribe\n        # operations performed by the retry layer continue to honor\n        # self.socket_timeout because they do not pass math.inf.\n        #\n        # TODO(next-major): when the async Connection.read_response\n        # default for ``timeout`` is changed to SENTINEL, passing","sourceCodeStart":1406,"sourceCodeEnd":1442,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/client.py#L1406-L1442","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\npubsub = client.pubsub()\nmsg = await pubsub.get_message(timeout=1.0)\n// after\npubsub = client.pubsub()\nawait pubsub.subscribe('my-channel')\nmsg = await pubsub.get_message(timeout=1.0)","handlingStrategy":"validation","validationCode":"if pubsub.connection is None:\n    await pubsub.subscribe('my-channel')\nawait pubsub.get_message(timeout=1.0)","typeGuard":"def is_subscribed(pubsub) -> bool:\n    return pubsub.connection is not None","tryCatchPattern":"try:\n    msg = await pubsub.get_message(timeout=1.0)\nexcept RuntimeError as e:\n    if 'pubsub connection not set' in str(e):\n        await pubsub.subscribe('my-channel')\n        msg = await pubsub.get_message(timeout=1.0)\n    else:\n        raise","preventionTips":["Centralize pubsub usage in a helper that always subscribes before reading.","Prefer pubsub.run(...) which connects for you."],"tags":["pubsub","lifecycle","subscribe","connection"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}