pika/pika · error · ValueError

Unexpected callback for asynchronous (nowait) operation.

Error message

Unexpected callback for asynchronous (nowait) operation.

What it means

_rpc enforces that a callback may only accompany non-empty acceptable_replies. A callback with empty/None replies is contradictory - there is no reply frame to dispatch to it (this is a nowait/asynchronous send), so pika raises ValueError to surface the inconsistency.

Source

Thrown at pika/channel.py:1467

        :param acceptable_replies: A (possibly empty) sequence of
            replies this RPC call expects or None
        """
        assert method.synchronous, (
            f'Only synchronous-capable methods may be used with _rpc: {method!r}'
        )

        # Validate we got None or a list of acceptable_replies
        if not isinstance(acceptable_replies, (type(None), list)):
            raise TypeError('acceptable_replies should be list or None')

        if callback is not None:
            # Validate the callback is callable
            if not callable(callback):
                raise TypeError('callback should be None or a callable')

            # Make sure that callback is accompanied by acceptable replies
            if not acceptable_replies:
                raise ValueError(
                    'Unexpected callback for asynchronous (nowait) operation.')

        # Make sure the channel is not closed yet
        if self.is_closed:
            self._raise_if_not_open()

        # If the channel is blocking, add subsequent commands to our stack
        if self._blocking:
            LOGGER.debug(
                'Already in blocking state, so enqueueing method %s; '
                'acceptable_replies=%r', method, acceptable_replies)
            self._blocked.append([method, callback, acceptable_replies])
            return

        # Note: _send_method can throw exceptions if there are framing errors
        # or invalid data passed in. Call it here to prevent self._blocking
        # from being set if an exception is thrown. This also prevents
        # acceptable_replies registering callbacks when exceptions are thrown

View on GitHub (pinned to 34a407b24f)

Solutions

  1. Either drop the callback, or supply the expected reply method class(es) in acceptable_replies
  2. For nowait ops, pass callback=None
  3. Do not set nowait=True when you need the reply

Example fix

# before (callback but no replies)
channel._rpc(method, on_reply, None)  # ValueError

# after (choose one)
channel._rpc(method, None, None)              # fire-and-forget
channel._rpc(method, on_reply, [spec.X.Ok])   # expect a reply
Defensive patterns

Strategy: validation

Validate before calling

def reconcile_callback_replies(callback, replies):
    if callback is not None and not replies:
        # cannot have a callback without replies; pick one
        return None, replies  # drop callback for nowait
    return callback, replies

cb, replies = reconcile_callback_replies(callback, replies)
channel._rpc(method, cb, replies)

Try / catch

try:
    channel._rpc(method, cb, replies)
except ValueError as e:
    if 'Unexpected callback' in str(e):
        channel._rpc(method, None, replies)  # nowait path
    else:
        raise

Prevention

When it happens

Trigger: Calling _rpc (or a channel method built on it) with a callback but acceptable_replies=None or []; mixing nowait=True with a completion callback.

Common situations: Requesting a callback on a nowait operation; a custom RPC helper that always attaches a callback regardless of replies.

Related errors


AI-assisted analysis of pika/pika@34a407b24f (2026-08-07). Data as JSON: /api/errors/6674f70e22025db6. Report an issue: GitHub.