JuliusBrussee/caveman · error · TypeError

scope resolver must return a Caveman Scope

Error message

scope resolver must return a Caveman Scope

What it means

_scope normalizes either a Scope instance or a callable resolver (invoked with ensure_config(config)) and enforces the result is a Caveman Scope. Any other return type — dict, tuple, None — cannot carry namespace/thread/branch identity, so a TypeError is raised. It backs state, recover, arecover, binding, and _options.

Solutions

  1. Make the resolver return a caveman_cloud.middleware Scope instance for every code path.
  2. Or pass a Scope instance directly instead of a callable.
  3. Check imports: use Scope from caveman_cloud.middleware, not another module.

Example fix

// before
def resolver(config): return {'thread': config['configurable']['thread_id']}
// after
from caveman_cloud.middleware import Scope
def resolver(config): return Scope('ns', config['configurable']['thread_id'])
Defensive patterns

Strategy: type-guard

Validate before calling

from caveman_cloud.middleware import Scope
resolved = scope if isinstance(scope, Scope) else scope(config)
assert isinstance(resolved, Scope), 'resolver must return caveman Scope'

Type guard

def resolves_to_scope(resolver, config):
    from caveman_cloud.middleware import Scope
    r = resolver if isinstance(resolver, Scope) else resolver(config)
    return isinstance(r, Scope)

Try / catch

try:
    out = chain.invoke(messages, config=config)
except TypeError as e:
    if 'Caveman Scope' in str(e):
        raise RuntimeError('scope resolver misconfigured; return caveman Scope') from e
    raise

Prevention

When it happens

Trigger: Passing a custom scope resolver callable to the LangChain adapter that returns something other than Scope (e.g. a dict, a tuple, or None on some branch), or returning a similarly-named Scope class from a different library.

Common situations: Custom resolver written before upgrading the library (old Scope signature); resolver that returns None when a config key is missing; importing Scope from the wrong package.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/langchain.py:43

from ._native import Attempt, manifest, owner, plain
from ._versions import matches_framework

ADAPTER = Adapter("langchain", "0.1.0", "1.4.0", "langchain-message-v1")


def scope_from_config(config: RunnableConfig, *, namespace: str) -> Scope:
    """Use the caller's checkpoint thread and explicit branch/epoch identity."""
    values = config.get("configurable", {})
    thread = values.get("thread_id")
    if not isinstance(thread, str) or not thread:
        raise ValueError("LangGraph middleware requires a nonempty configurable.thread_id")
    return Scope(namespace, thread, values.get("caveman_branch_id", "main"), values.get("caveman_cache_epoch", "0"))


def _scope(source, config=None):
    result = source if isinstance(source, Scope) else source(ensure_config(config))
    if not isinstance(result, Scope):
        raise TypeError("scope resolver must return a Caveman Scope")
    return result


def _message_view(messages, prefix=()):
    try:
        context = manifest([m.model_dump(mode="json") for m in [*prefix, *messages]])
    except (TypeError, ValueError, AttributeError):
        return None
    if context is None:
        return None
    candidates, setters = [], {}
    names = {call["id"]: call["name"] for message in messages for call in getattr(message, "tool_calls", [])
             if plain(call) and type(call.get("id")) is str and type(call.get("name")) is str}
    for mi, message in enumerate(messages):
        if type(message) is not ToolMessage or message.name == "caveman_retrieve" or names.get(message.tool_call_id) == "caveman_retrieve" or message.status == "error":
            continue
        if not message.name and message.tool_call_id not in names:
            continue

View on GitHub (pinned to 3ee70a1026)