{"record":{"id":"735954b790dae352","repo":"JuliusBrussee/caveman","slug":"caveman-recovery-tool-is-immutable","errorCode":null,"errorMessage":"Caveman recovery tool is immutable","messagePattern":"Caveman recovery tool is immutable","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/llama_index.py","lineNumber":106,"sourceCode":"class _FrozenRecoveryMetadata(_RecoveryMetadata):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        object.__setattr__(self, \"_caveman_frozen\", True)\n\n    def __setattr__(self, name, value):\n        if getattr(self, \"_caveman_frozen\", False):\n            raise AttributeError(\"Caveman recovery metadata is immutable\")\n        super().__setattr__(name, value)\n\n\nclass _FrozenFunctionTool(FunctionTool):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        object.__setattr__(self, \"_caveman_frozen\", True)\n\n    def __setattr__(self, name, value):\n        if getattr(self, \"_caveman_frozen\", False):\n            raise AttributeError(\"Caveman recovery tool is immutable\")\n        super().__setattr__(name, value)\n\n\nclass _Recovery:\n    def __init__(self, runtime, scope):\n        self.runtime, self.scope = runtime, scope\n\n        def recover(ctx: Context, handle: str, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            page = runtime.retrieve(_scope(scope, ctx), handle=handle, offset=offset, limit=limit, query=query)\n            return json.dumps(page, ensure_ascii=False)\n\n        async def arecover(ctx: Context, handle: str, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            page = await _async_runtime(runtime).retrieve(_scope(scope, ctx), handle=handle, offset=offset, limit=limit, query=query)\n            return json.dumps(page, ensure_ascii=False)\n\n        # FunctionTool inspects runtime annotations for its public context injection.\n        recover.__annotations__[\"ctx\"] = Context\n        arecover.__annotations__[\"ctx\"] = Context","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/llama_index.py#L88-L124","documentation":"_FrozenFunctionTool is a FunctionTool subclass frozen after construction; assigning any attribute on it raises AttributeError with this message so the caveman recovery tool cannot be altered once registered.","triggerScenarios":"Setting any attribute on the caveman_retrieve FunctionTool instance after creation, e.g. tool.metadata = ... or tool.custom_flag = True.","commonSituations":"Framework or user code that renames tools, decorates them, or mutates metadata of all tools in a registry before dispatch.","solutions":["Do not mutate the recovery tool; wrap or replace it with your own FunctionTool if customization is needed","Create a new FunctionTool.from_defaults with the desired settings and use that instead","Bypass with object.__setattr__ only as a deliberate last resort"],"exampleFix":"// before\ncaveman_tool.fn = my_wrapper(caveman_tool.fn)\n// after\nwrapped_tool = FunctionTool.from_defaults(async_fn=my_wrapper(original_fn), name=\"caveman_retrieve\")","handlingStrategy":"validation","validationCode":"if getattr(tool, \"_caveman_frozen\", False):\n    raise RuntimeError(\"cannot mutate caveman recovery tool; build a new FunctionTool instead\")","typeGuard":"def is_frozen_tool(tool) -> bool:\n    return getattr(tool, \"_caveman_frozen\", False)","tryCatchPattern":null,"preventionTips":["Never mutate registered tools; construct replacements instead","Keep the caveman_retrieve tool out of your own customization passes","Snapshot metadata you need at setup time"],"tags":["python","immutability","llamaindex"],"backgroundTag":"unsupported-operation","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"}