JuliusBrussee/caveman · error · TypeError

LlamaIndex scope resolver must return a Caveman Scope

Error message

LlamaIndex scope resolver must return a Caveman Scope

What it means

_scope accepts either a Scope instance or a resolver callable invoked with the workflow Context; it then validates the result. If the callable returns anything that is not a caveman_cloud.middleware.Scope (None, dict, str, framework object), it raises TypeError — recovery and tool gating need a real Scope to attribute calls.

Solutions

  1. Make the resolver return caveman_cloud.middleware.Scope(...) in every branch, including error/empty paths
  2. If a miss is possible, raise or substitute a default Scope instead of returning None
  3. Convert framework context data into a Scope explicitly before returning it
  4. Check for duplicate caveman-cloud versions (pip show caveman-cloud) so isinstance matches one class

Example fix

// before
def resolve(ctx):
    return ctx.store.get("session")  # may be a dict or None

// after
from caveman_cloud.middleware import Scope
def resolve(ctx):
    session = ctx.store.get("session")
    return Scope(subject=session["user"], call_type="agent_step") if session else Scope.anonymous()
Defensive patterns

Strategy: type-guard

Validate before calling

result = resolver(context) if not isinstance(resolver, Scope) else resolver
if not isinstance(result, Scope):
    raise TypeError("scope resolver must return caveman_cloud.middleware.Scope")

Type guard

def resolves_to_scope(resolver, context=None) -> bool:
    from caveman_cloud.middleware import Scope
    candidate = resolver if isinstance(resolver, Scope) else resolver(context)
    return isinstance(candidate, Scope)

Try / catch

try:
    scope = adapter_step(scope_resolver, context)
except TypeError as e:
    if "must return a Caveman Scope" in str(e):
        scope = default_scope()  # construct a valid Scope and continue/degrade

Prevention

When it happens

Trigger: Passing a scope resolver function whose return value is not a Scope — e.g. returning None on a cache miss, returning a dict of user claims, or returning a string session id — to recover/arecover/_options/take_step or the reader path.

Common situations: Resolver written before the Scope class existed and returning legacy dict context; early-return None when workflow context is empty; returning result.value or context metadata instead of a constructed Scope; duplicate caveman-cloud installs causing a different Scope class.

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/065bab14e89d5495. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/llama_index.py:46

from caveman_cloud.middleware import Adapter, Candidate, MiddlewareError, MiddlewareRuntime, RecoveryBinding, Scope
from caveman_cloud.middleware.runtime import RECOVERY_DESCRIPTION, RECOVERY_SCHEMA
from ._native import Attempt, manifest, owner
from ._usage import usage
from ._versions import matches_framework, supports_framework

ADAPTER = Adapter("llama-index", "0.1.0", "0.14.24", "llama-index-message-v1")
RAG_ADAPTER = Adapter("llama-index-rag", "0.1.0", "0.14.24", "llama-index-node-v1")


def _check_version(runtime):
    return supports_framework(runtime, ("llama-index-core", "0.14", "0.15"))


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


def _async_runtime(runtime):
    return runtime.as_async() if isinstance(runtime, MiddlewareRuntime) else runtime


def _protocol(model, runtime):
    provider = (type(model).__module__, type(model).__name__)
    supported = {
        ("llama_index.llms.openai.base", "OpenAI"): ("llama-index-llms-openai", "0.8", "1", "openai-chat"),
        ("llama_index.llms.anthropic.base", "Anthropic"): ("llama-index-llms-anthropic", "0.12", "1", "anthropic-messages"),
    }
    match = supported.get(provider)
    if match and not supports_framework(runtime, match[:3]):
        return None
    if match:
        sdk = ("openai", "2.54", "4") if match[3] == "openai-chat" else ("anthropic", "0.125", "2")

View on GitHub (pinned to 3ee70a1026)