headroomlabs-ai/headroom · warning · AttributeError

{name} is read-only; mutate its fields instead

Error message

{name} is read-only; mutate its fields instead

What it means

AttributeError raised by HeadroomConfig's __setattr__ (headroom/testing/harness.py:564) when code tries to rebind the composite attributes 'headroom' or 'proxy' on the harness config object. These hold the nested Headroom/Proxy config objects and are deliberately read-only — you are meant to mutate their fields (config.proxy.mode = ...) or use the documented setters, not replace the objects wholesale (which would silently strand previously applied overrides).

Source

Thrown at headroom/testing/harness.py:564

        return cast(str, self.proxy.mode)

    @mode.setter
    def mode(self, value: Literal["token", "cache"]) -> None:
        if value not in {"token", "cache"}:
            raise ValueError("mode must be 'token' or 'cache'")
        self.proxy.mode = value

    @property
    def default_mode(self) -> HeadroomMode:
        return self.headroom.default_mode

    @default_mode.setter
    def default_mode(self, value: str | HeadroomMode) -> None:
        self.headroom.default_mode = _coerce_headroom_mode(value)

    def __setattr__(self, name: str, value: Any) -> None:
        if name in {"headroom", "proxy"}:
            raise AttributeError(f"{name} is read-only; mutate its fields instead")
        descriptor = getattr(type(self), name, None)
        if isinstance(descriptor, property) and descriptor.fset is not None:
            descriptor.fset(self, value)
            return
        if hasattr(self.proxy, name):
            setattr(self.proxy, name, value)
            return
        if hasattr(self.headroom, name):
            setattr(self.headroom, name, value)
            return
        raise AttributeError(f"unknown Headroom harness config field: {name}")


class Headroom:
    """Fluent scenario builder.

    Example:
        ``Headroom.with_bedrock(region="us-east-1").on_apple_silicon().configure(...)``

View on GitHub (pinned to 322425c43b)

Solutions

  1. Mutate fields instead: harness.config.proxy.mode = 'cache' or use configure_proxy(...).
  2. To swap an entire sub-config, build a fresh Headroom harness rather than rebinding the attribute.
  3. Teach generic copiers to skip 'headroom'/'proxy' attributes.

Example fix

# before
harness.config.proxy = ProxyConfig(mode='cache')  # AttributeError: read-only

# after
harness.configure_proxy(mode='cache')  # or harness.config.proxy.mode = 'cache'
Defensive patterns

Strategy: type-guard

Validate before calling

if name in {'headroom', 'proxy'}:
    raise RuntimeError(f'{name} is read-only on the harness config; mutate fields instead')

Prevention

When it happens

Trigger: harness.config.proxy = ProxyConfig(...) — replacing the whole sub-config after having configured fields on it; frameworks that auto-assign attributes generically (e.g. dataclasses.replace or copy utilities) hitting the 'proxy'/'headroom' names.

Common situations: Test fixtures that rebuild configs mid-test; generic deep-copy/replace helpers that rebind every attribute; IDE-generated assignment refactors.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/e01930d4e0d51aa7. Report an issue: GitHub.