{"record":{"id":"e01930d4e0d51aa7","repo":"headroomlabs-ai/headroom","slug":"name-is-read-only-mutate-its-fields-instead","errorCode":null,"errorMessage":"{name} is read-only; mutate its fields instead","messagePattern":"(.+?) is read-only; mutate its fields instead","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"warning","filePath":"headroom/testing/harness.py","lineNumber":564,"sourceCode":"        return cast(str, self.proxy.mode)\n\n    @mode.setter\n    def mode(self, value: Literal[\"token\", \"cache\"]) -> None:\n        if value not in {\"token\", \"cache\"}:\n            raise ValueError(\"mode must be 'token' or 'cache'\")\n        self.proxy.mode = value\n\n    @property\n    def default_mode(self) -> HeadroomMode:\n        return self.headroom.default_mode\n\n    @default_mode.setter\n    def default_mode(self, value: str | HeadroomMode) -> None:\n        self.headroom.default_mode = _coerce_headroom_mode(value)\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        if name in {\"headroom\", \"proxy\"}:\n            raise AttributeError(f\"{name} is read-only; mutate its fields instead\")\n        descriptor = getattr(type(self), name, None)\n        if isinstance(descriptor, property) and descriptor.fset is not None:\n            descriptor.fset(self, value)\n            return\n        if hasattr(self.proxy, name):\n            setattr(self.proxy, name, value)\n            return\n        if hasattr(self.headroom, name):\n            setattr(self.headroom, name, value)\n            return\n        raise AttributeError(f\"unknown Headroom harness config field: {name}\")\n\n\nclass Headroom:\n    \"\"\"Fluent scenario builder.\n\n    Example:\n        ``Headroom.with_bedrock(region=\"us-east-1\").on_apple_silicon().configure(...)``","sourceCodeStart":546,"sourceCodeEnd":582,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/testing/harness.py#L546-L582","documentation":"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).","triggerScenarios":"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.","commonSituations":"Test fixtures that rebuild configs mid-test; generic deep-copy/replace helpers that rebind every attribute; IDE-generated assignment refactors.","solutions":["Mutate fields instead: harness.config.proxy.mode = 'cache' or use configure_proxy(...).","To swap an entire sub-config, build a fresh Headroom harness rather than rebinding the attribute.","Teach generic copiers to skip 'headroom'/'proxy' attributes."],"exampleFix":"# before\nharness.config.proxy = ProxyConfig(mode='cache')  # AttributeError: read-only\n\n# after\nharness.configure_proxy(mode='cache')  # or harness.config.proxy.mode = 'cache'","handlingStrategy":"type-guard","validationCode":"if name in {'headroom', 'proxy'}:\n    raise RuntimeError(f'{name} is read-only on the harness config; mutate fields instead')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the fluent configure()/configure_proxy()/configure_headroom() builders.","Treat sub-config objects as shared state to mutate, never to rebind."],"tags":["testing","harness","api-misuse"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}