{"record":{"id":"f54503c4f37af3a0","repo":"JuliusBrussee/caveman","slug":"recovery-unavailable","errorCode":"recovery_unavailable","errorMessage":"recovery_unavailable","messagePattern":"recovery_unavailable","errorType":"error_code","errorClass":"MiddlewareError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/agno.py","lineNumber":145,"sourceCode":"\nclass _Connection:\n    def __init__(self, runtime, scope):\n        self.sync = runtime if isinstance(runtime, MiddlewareRuntime) else None\n        self.async_runtime = runtime.as_async() if self.sync else runtime\n        self.scope, self.recovery_tool = scope, None\n        self.active = contextvars.ContextVar(\"caveman_agno_run\", default=None)\n        self.version_supported = matches_framework((\"agno\", \"3.0\", \"4\"))\n        if not self.version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n\n    def passive(self, runtime, reason):\n        return Attempt(runtime, None, str(uuid.uuid4()), str(uuid.uuid4()), passive=True, reason=reason, adapter=ADAPTER.id), {}, None, None\n\n    def register(self):\n        def recover(handle: str, run_context: RunContext, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            frame = self.active.get()\n            if frame is None:\n                raise MiddlewareError(\"recovery_unavailable\")\n            if self.sync is None:\n                raise TypeError(\"Synchronous Agno recovery requires MiddlewareRuntime\")\n            if run_context is not None:\n                raise_if_cancelled(run_context.run_id)\n            return json.dumps(self.sync.retrieve(frame.scope, handle=handle, offset=offset, limit=limit, query=query),\n                              ensure_ascii=False, separators=(\",\", \":\"))\n\n        async def arecover(handle: str, run_context: RunContext, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            frame = self.active.get()\n            if frame is None:\n                raise MiddlewareError(\"recovery_unavailable\")\n            if run_context is not None:\n                await araise_if_cancelled(run_context.run_id)\n            return json.dumps(await self.async_runtime.retrieve(frame.scope, handle=handle, offset=offset, limit=limit, query=query),\n                              ensure_ascii=False, separators=(\",\", \":\"))\n\n        # Agno preserves a Function's entrypoint identity in its per-run copy.\n        # Explicit processing avoids schema rewriting and keeps RunContext hidden.","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/agno.py#L127-L163","documentation":"In the Agno adapter's synchronous recover callback, the middleware keeps the current execution frame in a context-local `active` slot. If `recover(handle, ...)` is invoked when no caveman attempt frame is active, the middleware cannot determine which scope/store to retrieve recorded context from, so it raises MiddlewareError('recovery_unavailable').","triggerScenarios":"Calling the registered `recover` function outside of a run managed by the Agno middleware, or after the frame was popped (e.g. recover invoked from a different thread/task than the one that entered the attempt).","commonSituations":"Wiring recover() into a custom retry handler that runs after the middleware frame closed; calling recover from a worker thread or separate coroutine where ContextVar was never set; invoking recover in a plain script without starting a middleware-wrapped run.","solutions":["Ensure recover() is only called inside a run that entered the middleware (active frame set)","Call recover from the same thread/context that started the caveman attempt, or capture the frame before spawning work","Wrap external recover calls with an explicit check/logging so failures degrade gracefully","Check library version/changelog for threading-context fixes and upgrade"],"exampleFix":"# before\nresult = json.loads(recover(handle, run_context))  # called outside middleware frame\n# after\n# only call recover inside the middleware-managed run / same context:\nwith middleware.run(scope=\"my-scope\"):\n    result = json.loads(recover(handle, run_context))","handlingStrategy":"try-catch","validationCode":"frame_ok = middleware_active()  # expose/check ContextVar presence before calling recover\nif not frame_ok:\n    raise RuntimeError('recover called outside a middleware run')","typeGuard":"def can_recover(adapter) -> bool:\n    return adapter.active.get() is not None","tryCatchPattern":"try:\n    payload = recover(handle, run_context)\nexcept MiddlewareError as e:\n    if str(e) == 'recovery_unavailable':\n        payload = None  # fall back: no active frame\n    else:\n        raise","preventionTips":["Only call recover inside the middleware-managed run","Avoid crossing thread/task boundaries without propagating contextvars","Log frame availability before recovery attempts in tests"],"tags":["python","middleware","recovery","context"],"backgroundTag":"invalid-state-transition","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"}