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
- Construct the scope with caveman_cloud.middleware.Scope (or obtain it from your runtime's scope factory) before calling the adapter
- Check for duplicate caveman-cloud installs (pip show caveman-cloud; pip install --force-reinstall caveman-cloud) so there is only one Scope class
- In hooks, resolve the real Scope from your framework context rather than passing raw request metadata
- 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
- Always construct Scope via caveman_cloud.middleware factories, never hand-rolled dicts
- Spec test mocks against the real Scope class
- Keep a single caveman-cloud version installed to avoid duplicate Scope classes
- Centralize scope construction in one helper used by all hooks
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
- Agno scope resolver must return a Caveman Scope
- AutoGen requires a stable Caveman Scope for each agent or…
- LiteLLM metadata must be a native dictionary
- Pydantic AI scope resolver must return a Caveman Scope
- Strands scope resolver must return a Caveman Scope
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)