{"id":"0f6fd59c6655827c","repo":"redis/redis-py","slug":"channel-channel-has-no-handler-registered","errorCode":null,"errorMessage":"Channel: '{channel}' has no handler registered","messagePattern":"Channel: '(.+?)' has no handler registered","errorType":"exception","errorClass":"PubSubError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/client.py","lineNumber":1743,"sourceCode":"        poll_timeout: float = 1.0,\n        pubsub=None,\n    ) -> None:\n        \"\"\"Process pub/sub messages using registered callbacks.\n\n        This is the equivalent of :py:meth:`redis.PubSub.run_in_thread` in\n        redis-py, but it is a coroutine. To launch it as a separate task, use\n        ``asyncio.create_task``:\n\n            >>> task = asyncio.create_task(pubsub.run())\n\n        To shut it down, use asyncio cancellation:\n\n            >>> task.cancel()\n            >>> await task\n        \"\"\"\n        for channel, handler in self.channels.items():\n            if handler is None:\n                raise PubSubError(f\"Channel: '{channel}' has no handler registered\")\n        for pattern, handler in self.patterns.items():\n            if handler is None:\n                raise PubSubError(f\"Pattern: '{pattern}' has no handler registered\")\n\n        await self.connect()\n        while True:\n            try:\n                if pubsub is None:\n                    await self.get_message(\n                        ignore_subscribe_messages=True, timeout=poll_timeout\n                    )\n                else:\n                    await pubsub.get_message(\n                        ignore_subscribe_messages=True, timeout=poll_timeout\n                    )\n            except asyncio.CancelledError:\n                raise\n            except BaseException as e:","sourceCodeStart":1725,"sourceCodeEnd":1761,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/client.py#L1725-L1761","documentation":"Raised by the async PubSub worker's run() method (redis/asyncio/client.py:1743) when iterating self.channels and finding a channel whose registered handler is None. run() is the callback-driven message pump; it requires every subscribed channel to have a callback, otherwise it cannot dispatch. It raises PubSubError naming the offending channel.","triggerScenarios":"Subscribing with the channel-only form `await pubsub.subscribe('ch')` (no callable) and then launching `await pubsub.run(...)` or `asyncio.create_task(pubsub.run())`. The callback form `pubsub.subscribe(**{'ch': handler})` is required for run().","commonSituations":"Mixing the manual get_message() style (no callbacks needed) with the run() worker style (callbacks mandatory); copy-pasting a subscribe() call into a run()-based example.","solutions":["Pass callbacks at subscribe time: `await pubsub.subscribe(**{'ch': my_handler})`.","If you want manual control, use `await pubsub.get_message(timeout=...)` instead of run().","Ensure every channel registered via subscribe has a non-None handler before calling run()."],"exampleFix":"// before\npubsub = r.pubsub()\nawait pubsub.subscribe('ch')\ntask = asyncio.create_task(pubsub.run())\n// after\nasync def my_handler(msg):\n    ...\npubsub = r.pubsub()\nawait pubsub.subscribe(**{'ch': my_handler})\ntask = asyncio.create_task(pubsub.run())","handlingStrategy":"validation","validationCode":"async def handler(msg): ...\npubsub = r.pubsub()\nawait pubsub.subscribe(**{'ch': handler})\nassert all(h is not None for h in pubsub.channels.values())\ntask = asyncio.create_task(pubsub.run())","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the callback form subscribe(**{name: fn}) when you intend to call run().","Before run(), assert every channel in pubsub.channels has a callable handler.","Do not mix manual get_message() subscriptions with run()."],"tags":["redis","asyncio","pubsub","callback-worker","api-misuse"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}