redis/redis-py · error · PubSubError

Channel: ' ' has no handler registered

Error message

Channel: '{channel}' has no handler registered

What it means

Raised at the start of PubSubWorker.run() when iterating self.channels: a channel is present in the subscription map but its handler callback is None. The run loop dispatches messages through registered handlers, so a subscribed channel with no handler is an unrecoverable programming error.

Solutions

  1. Subscribe with a real callback: await pubsub.subscribe(**{'my-channel': my_handler}).
  2. If you intend to poll messages yourself (no handlers), use get_message in a loop instead of run().
  3. Before run(), assert none of pubsub.channels.values() is None.

Example fix

// before
await pubsub.subscribe('my-channel')
await pubsub.run()
// after
async def handler(msg): ...
await pubsub.subscribe(**{'my-channel': handler})
await pubsub.run()
Defensive patterns

Strategy: validation

Validate before calling

missing = [ch for ch, h in pubsub.channels.items() if h is None]
assert not missing, f'channels without handlers: {missing}'
await pubsub.run()

Type guard

def all_channels_have_handlers(pubsub) -> bool:
    return all(h is not None for h in pubsub.channels.values())

Try / catch

try:
    await pubsub.run()
except PubSubError as e:
    if 'no handler registered' in str(e):
        # re-subscribe with handlers, then run again
        ...

Prevention

When it happens

Trigger: Calling pubsub.subscribe(**{channel: callback}) where the callback value is None, then awaiting pubsub.run(...); or mixing positional subscribe('ch') (which leaves handler None) with the callback-based run() API.

Common situations: Switching from the manual get_message polling style to the run()-with-handlers style without re-subscribing with handlers; passing a typo'd variable that resolves to None as the handler.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/0f6fd59c6655827c. Report an issue: GitHub.

Appendix: 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 6a6b581b48)