{"record":{"id":"ad51b05f910e7031","repo":"JuliusBrussee/caveman","slug":"litellm-scope-must-be-a-trusted-caveman-scope","errorCode":null,"errorMessage":"LiteLLM scope must be a trusted Caveman Scope","messagePattern":"LiteLLM scope must be a trusted Caveman Scope","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/litellm.py","lineNumber":94,"sourceCode":"\n    def __exit__(self, *_):\n        with _registration_lock:\n            self._registrations = max(0, self._registrations - 1)\n            if self._registrations == 0:\n                native.logging_callback_manager.remove_callback_from_all_lists(self)\n                native.logging_callback_manager.remove_callback_from_list_by_object(native.input_callback, self, require_self=False)\n\n    def close(self):\n        with _registration_lock:\n            self._registrations = 1\n            self.__exit__()\n        with self._lock:\n            self._requests.clear()\n            self._attempts.clear()\n\n    def _remember(self, scope, call_type=None):\n        if not isinstance(scope, Scope):\n            raise TypeError(\"LiteLLM scope must be a trusted Caveman Scope\")\n        request = _Request(scope, str(uuid.uuid4()), time.monotonic() + 3600, protocol=self._protocol(call_type))\n        if self.operator_recovery:\n            supplied = self.operator_recovery(scope)\n            if supplied is not None:\n                request.binding, request.overhead = supplied\n                if not self.async_runtime.owns_binding(request.binding, scope):\n                    raise ValueError(\"Recovery executor belongs to another runtime or scope\")\n        key = uuid.uuid4().hex\n        with self._lock:\n            now = time.monotonic()\n            for old in list(self._requests):\n                if self._requests[old].expires <= now:\n                    del self._requests[old]\n            while len(self._requests) >= 1024:\n                self._requests.popitem(last=False)\n            self._requests[key] = request\n        return key, request\n","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/litellm.py#L76-L112","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait adapter.async_pre_call_hook(scope={\"request_id\": rid}, ...)\n\n// after\nfrom caveman_cloud.middleware import Scope\nscope = Scope(subject=user_id, call_type=\"completion\")\nawait adapter.async_pre_call_hook(scope=scope, ...)","handlingStrategy":"type-guard","validationCode":"from caveman_cloud.middleware import Scope\nif not isinstance(scope, Scope):\n    raise TypeError(\"scope must be a caveman_cloud Scope before calling the adapter\")","typeGuard":"def is_caveman_scope(obj) -> bool:\n    from caveman_cloud.middleware import Scope\n    return isinstance(obj, Scope)","tryCatchPattern":"try:\n    await adapter.async_pre_call_hook(scope=scope, ...)\nexcept TypeError as e:\n    if \"trusted Caveman Scope\" in str(e):\n        scope = build_scope_from_context(request)\n        await adapter.async_pre_call_hook(scope=scope, ...)","preventionTips":["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"],"tags":["python","type-error","scope","litellm"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}