JuliusBrussee/caveman · error · TypeError

LiteLLM scope must be a trusted Caveman Scope

Error message

LiteLLM scope must be a trusted Caveman Scope

What it means

_remember registers each LiteLLM call under a caveman Scope; it validates with isinstance(scope, Scope) and raises TypeError if the object is anything else. This guards against untrusted/forged scope objects reaching the recovery and tracking machinery, since Scopes carry identity and binding data the runtime relies on.

Solutions

  1. Construct the scope with caveman_cloud.middleware.Scope (or obtain it from your runtime's scope factory) before calling the adapter
  2. Check for duplicate caveman-cloud installs (pip show caveman-cloud; pip install --force-reinstall caveman-cloud) so there is only one Scope class
  3. In hooks, resolve the real Scope from your framework context rather than passing raw request metadata
  4. If using mocks in tests, spec them against the real Scope class

Example fix

// before
await adapter.async_pre_call_hook(scope={"request_id": rid}, ...)

// after
from caveman_cloud.middleware import Scope
scope = Scope(subject=user_id, call_type="completion")
await adapter.async_pre_call_hook(scope=scope, ...)
Defensive patterns

Strategy: type-guard

Validate before calling

from caveman_cloud.middleware import Scope
if not isinstance(scope, Scope):
    raise TypeError("scope must be a caveman_cloud Scope before calling the adapter")

Type guard

def is_caveman_scope(obj) -> bool:
    from caveman_cloud.middleware import Scope
    return isinstance(obj, Scope)

Try / catch

try:
    await adapter.async_pre_call_hook(scope=scope, ...)
except TypeError as e:
    if "trusted Caveman Scope" in str(e):
        scope = build_scope_from_context(request)
        await adapter.async_pre_call_hook(scope=scope, ...)

Prevention

When it happens

Trigger: Calling the adapter (async_pre_call_hook / _activation path) with a scope argument that is a dict, a foreign framework object, a mock, or a different library's Scope class instead of caveman_cloud.middleware.Scope.

Common situations: Hand-rolling pre-call hooks and passing user_id/request dicts as 'scope'; using a test double that mimics Scope without subclassing it; upgrading caveman-cloud so there are two distinct Scope classes from different installed versions.

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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/litellm.py:94

    def __exit__(self, *_):
        with _registration_lock:
            self._registrations = max(0, self._registrations - 1)
            if self._registrations == 0:
                native.logging_callback_manager.remove_callback_from_all_lists(self)
                native.logging_callback_manager.remove_callback_from_list_by_object(native.input_callback, self, require_self=False)

    def close(self):
        with _registration_lock:
            self._registrations = 1
            self.__exit__()
        with self._lock:
            self._requests.clear()
            self._attempts.clear()

    def _remember(self, scope, call_type=None):
        if not isinstance(scope, Scope):
            raise TypeError("LiteLLM scope must be a trusted Caveman Scope")
        request = _Request(scope, str(uuid.uuid4()), time.monotonic() + 3600, protocol=self._protocol(call_type))
        if self.operator_recovery:
            supplied = self.operator_recovery(scope)
            if supplied is not None:
                request.binding, request.overhead = supplied
                if not self.async_runtime.owns_binding(request.binding, scope):
                    raise ValueError("Recovery executor belongs to another runtime or scope")
        key = uuid.uuid4().hex
        with self._lock:
            now = time.monotonic()
            for old in list(self._requests):
                if self._requests[old].expires <= now:
                    del self._requests[old]
            while len(self._requests) >= 1024:
                self._requests.popitem(last=False)
            self._requests[key] = request
        return key, request

View on GitHub (pinned to 3ee70a1026)