{"record":{"id":"821915626f9a8d9d","repo":"JuliusBrussee/caveman","slug":"recovery-executor-belongs-to-another-runtime-or-scope","errorCode":null,"errorMessage":"Recovery executor belongs to another runtime or scope","messagePattern":"Recovery executor belongs to another runtime or scope","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/litellm.py","lineNumber":101,"sourceCode":"\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\n    def _request(self, kwargs):\n        params = kwargs.get(\"litellm_params\")\n        params = params if plain(params) else {}\n        with self._lock:\n            for metadata in (kwargs.get(\"litellm_metadata\"), kwargs.get(\"metadata\"), params.get(\"litellm_metadata\"), params.get(\"metadata\")):\n                key = metadata.get(_KEY) if plain(metadata) else None\n                request = self._requests.get(key) if type(key) is str else None","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/litellm.py#L83-L119","documentation":"When operator_recovery supplies a recovery binding, _remember asks the async runtime whether it owns that binding for this scope (owns_binding). If not, the binding came from another MiddlewareRuntime instance or a different scope, and the adapter raises ValueError rather than mixing recovery executors across runtimes.","triggerScenarios":"An operator_recovery callback returns a RecoveryBinding produced by a different MiddlewareRuntime instance, or cached from a previous app run / different scope, and the current async_runtime.owns_binding(binding, scope) returns False.","commonSituations":"Creating a second runtime (e.g. after reload or in tests) while reusing a pickled/cached recovery binding; wiring one shared recovery executor across multiple runtimes; copying bindings between environments.","solutions":["Re-obtain recovery bindings from the same MiddlewareRuntime that is handling the call","Rebuild the operator_recovery callback so it uses the current runtime's recovery API instead of cached bindings","In dev/test, clear cached bindings on runtime recreation","Confirm only one runtime instance is constructed per app lifecycle"],"exampleFix":"// before\nold_binding = cached_bindings[scope.key]  # from another runtime\nadapter = CavemanLiteLLM(runtime=runtime_b, operator_recovery=lambda s: old_binding)\n\n// after\nadapter = CavemanLiteLLM(runtime=runtime_b, operator_recovery=runtime_b.make_recovery_binding)","handlingStrategy":"validation","validationCode":"if supplied is not None and not async_runtime.owns_binding(supplied_binding, scope):\n    supplied_binding = None  # drop stale binding; let the runtime mint a fresh one","typeGuard":"def binding_owned(binding, scope, runtime) -> bool:\n    try:\n        return bool(runtime.owns_binding(binding, scope))\n    except Exception:\n        return False","tryCatchPattern":"try:\n    with adapter.activation(scope, kwargs, method):\n        ...\nexcept ValueError as e:\n    if \"another runtime or scope\" in str(e):\n        refresh_recovery_bindings(runtime)  # rebuild from the current runtime\n        retry_once()","preventionTips":["Never cache or pickle recovery bindings across runtime instances or process restarts","Bind operator_recovery to the specific runtime it serves","Rebuild recovery state in test fixtures whenever a new runtime is created","Use one MiddlewareRuntime per application lifecycle"],"tags":["python","value-error","recovery-binding","runtime-ownership","litellm"],"backgroundTag":"invalid-argument-value","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"}