redis/redis-py · error · PubSubError

Channel: '{channel}' has no handler registered

Error message

Channel: '{channel}' has no handler registered

What it means

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.

Source

Thrown at redis/asyncio/client.py:1743

        poll_timeout: float = 1.0,
        pubsub=None,
    ) -> None:
        """Process pub/sub messages using registered callbacks.

        This is the equivalent of :py:meth:`redis.PubSub.run_in_thread` in
        redis-py, but it is a coroutine. To launch it as a separate task, use
        ``asyncio.create_task``:

            >>> task = asyncio.create_task(pubsub.run())

        To shut it down, use asyncio cancellation:

            >>> task.cancel()
            >>> await task
        """
        for channel, handler in self.channels.items():
            if handler is None:
                raise PubSubError(f"Channel: '{channel}' has no handler registered")
        for pattern, handler in self.patterns.items():
            if handler is None:
                raise PubSubError(f"Pattern: '{pattern}' has no handler registered")

        await self.connect()
        while True:
            try:
                if pubsub is None:
                    await self.get_message(
                        ignore_subscribe_messages=True, timeout=poll_timeout
                    )
                else:
                    await pubsub.get_message(
                        ignore_subscribe_messages=True, timeout=poll_timeout
                    )
            except asyncio.CancelledError:
                raise
            except BaseException as e:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Pass callbacks at subscribe time: `await pubsub.subscribe(**{'ch': my_handler})`.
  2. If you want manual control, use `await pubsub.get_message(timeout=...)` instead of run().
  3. Ensure every channel registered via subscribe has a non-None handler before calling run().

Example fix

// before
pubsub = r.pubsub()
await pubsub.subscribe('ch')
task = asyncio.create_task(pubsub.run())
// after
async def my_handler(msg):
    ...
pubsub = r.pubsub()
await pubsub.subscribe(**{'ch': my_handler})
task = asyncio.create_task(pubsub.run())
Defensive patterns

Strategy: validation

Validate before calling

async def handler(msg): ...
pubsub = r.pubsub()
await pubsub.subscribe(**{'ch': handler})
assert all(h is not None for h in pubsub.channels.values())
task = asyncio.create_task(pubsub.run())

Prevention

When it happens

Trigger: 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().

Common situations: 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.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/0f6fd59c6655827c.json. Report an issue: GitHub.