JuliusBrussee/caveman · error · TypeError

Match the sync/async runtime to the chat

Error message

Match the sync/async runtime to the chat

What it means

with_caveman_google_chat requires the runtime kind to match the chat flavor: sync MiddlewareRuntime for Chat, AsyncMiddlewareRuntime for AsyncChat. It auto-converts a sync runtime when the chat is async, but if after that conversion the runtime still does not match the chat's sync/async flavor, this TypeError is raised.

Solutions

  1. For a sync Chat, pass a MiddlewareRuntime; for an AsyncChat, pass AsyncMiddlewareRuntime or a sync runtime (auto-converted).
  2. Convert explicitly with runtime.as_async() when pairing with AsyncChat.
  3. Construct a proper sync MiddlewareRuntime instead of reusing the async one for sync chats.

Example fix

// before
async_runtime = sync_runtime.as_async()
with_caveman_google_chat(sync_chat, async_runtime, scope)  # mismatch
// after
with_caveman_google_chat(sync_chat, sync_runtime, scope)
Defensive patterns

Strategy: type-guard

Validate before calling

from google.genai.chats import Chat, AsyncChat
from caveman_cloud.middleware import MiddlewareRuntime, AsyncMiddlewareRuntime
expected = AsyncMiddlewareRuntime if isinstance(chat, AsyncChat) else MiddlewareRuntime
if not isinstance(runtime, expected):
    raise TypeError("Runtime sync/async flavor must match the chat")

Type guard

def runtime_matches_chat(chat, runtime) -> bool:
    from google.genai.chats import Chat, AsyncChat
    from caveman_cloud.middleware import MiddlewareRuntime, AsyncMiddlewareRuntime
    if isinstance(chat, AsyncChat):
        return isinstance(runtime, (AsyncMiddlewareRuntime, MiddlewareRuntime))
    return isinstance(runtime, MiddlewareRuntime) and not isinstance(runtime, AsyncMiddlewareRuntime)

Try / catch

try:
    chat = with_caveman_google_chat(chat, runtime, scope)
except TypeError as e:
    if "sync/async runtime" in str(e):
        runtime = sync_runtime.as_async() if isinstance(chat, AsyncChat) else sync_runtime
        chat = with_caveman_google_chat(chat, runtime, scope)
    else:
        raise

Prevention

When it happens

Trigger: Passing an AsyncMiddlewareRuntime with a synchronous Chat (auto-conversion only goes sync->async), or a runtime object that is neither MiddlewareRuntime nor AsyncMiddlewareRuntime (e.g. a dict/string/None).

Common situations: Reusing the async runtime (created for client.aio) with a synchronous chat; passing a plain dict config as runtime; a custom runtime class that doesn't subclass either expected runtime type.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/99d46bfa5659ba5f. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/google.py:185

        return client
    _wrap_model(client.models, runtime, scope)
    _wrap_model(client.aio.models, runtime.as_async(), scope, True)
    return client


def with_caveman_google_chat(chat, *, runtime, scope, config=None):
    """Register recovery in an existing native Chat/AsyncChat's own AFC loop.

    Pass the original default config here, or supply config on each send. No
    private chat configuration/history is read or modified.
    """
    asynchronous = isinstance(chat, AsyncChat)
    if not isinstance(chat, (Chat, AsyncChat)):
        raise TypeError("Expected a native Google Chat or AsyncChat")
    if asynchronous and isinstance(runtime, MiddlewareRuntime):
        runtime = runtime.as_async()
    if not isinstance(runtime, AsyncMiddlewareRuntime if asynchronous else MiddlewareRuntime):
        raise TypeError("Match the sync/async runtime to the chat")
    if not supports_framework(runtime, ("google-genai", "2.22", "3")):
        return chat
    send, stream, default = chat.send_message, chat.send_message_stream, config
    if asynchronous:
        @functools.wraps(send)
        async def send_message(message, config=None):
            selected, context = _bind(config if config is not None else default, runtime, scope, asynchronous=True)
            token = _invocation.set(context)
            try:
                return await send(message, config=selected)
            finally:
                _invocation.reset(token)

        @functools.wraps(stream)
        async def send_message_stream(message, config=None):
            selected, context = _bind(config if config is not None else default, runtime, scope, asynchronous=True)
            token = _invocation.set(context)
            try:

View on GitHub (pinned to 3ee70a1026)