JuliusBrussee/caveman · error · AttributeError

Caveman recovery metadata is immutable

Error message

Caveman recovery metadata is immutable

What it means

_FrozenModel subclasses LlamaIndex's Pydantic BaseModel and sets a private flag after init; any subsequent attribute assignment on the instance raises AttributeError because recovery metadata objects must not be mutated after construction. This protects the bookkeeping state the caveman runtime relies on.

Solutions

  1. Treat the object as read-only; construct a new instance with the desired values instead of mutating
  2. Use object.__setattr__(obj, name, value) only if you explicitly must bypass the freeze (not recommended)
  3. If you own the wrapping code, extract needed values at construction time and keep your own mutable copy

Example fix

// before
recovery_meta.name = "renamed"
// after
new_meta = type(recovery_meta)(**{**recovery_meta.model_dump(), "name": "renamed"})
Defensive patterns

Strategy: validation

Validate before calling

def can_set(obj, name):
    return not getattr(obj, "_caveman_frozen", False)
# check before assigning
if not can_set(recovery_meta, "name"):
    recovery_meta = type(recovery_meta)(**{**recovery_meta.model_dump(), "name": new_name})

Type guard

def is_frozen(obj) -> bool:
    return getattr(obj, "_caveman_frozen", False)

Prevention

When it happens

Trigger: Assigning any attribute (e.g. recovery_obj.some_field = x or recovery_obj.new_attr = 1) on a caveman-created recovery metadata/model instance after it has been constructed.

Common situations: Patching tool metadata in place, applying generic middleware that copies/rewrites attributes on LlamaIndex objects, or framework code that updates model fields post-init.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/2bd34a3739a6eb84. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/llama_index.py:95


_invocation = contextvars.ContextVar("caveman_llama_index_invocation", default=None)


class _RecoveryMetadata(ToolMetadata):
    def get_parameters_dict(self):
        # Default ToolMetadata removes additionalProperties from the schema.
        return copy.deepcopy(RECOVERY_SCHEMA)


class _FrozenRecoveryMetadata(_RecoveryMetadata):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        object.__setattr__(self, "_caveman_frozen", True)

    def __setattr__(self, name, value):
        if getattr(self, "_caveman_frozen", False):
            raise AttributeError("Caveman recovery metadata is immutable")
        super().__setattr__(name, value)


class _FrozenFunctionTool(FunctionTool):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        object.__setattr__(self, "_caveman_frozen", True)

    def __setattr__(self, name, value):
        if getattr(self, "_caveman_frozen", False):
            raise AttributeError("Caveman recovery tool is immutable")
        super().__setattr__(name, value)


class _Recovery:
    def __init__(self, runtime, scope):
        self.runtime, self.scope = runtime, scope

View on GitHub (pinned to 3ee70a1026)