{"record":{"id":"03e0465a022f902d","repo":"home-assistant/core","slug":"system-generated-users-cannot-disable-multi-factor","errorCode":null,"errorMessage":"System generated users cannot disable multi-factor auth module.","messagePattern":"System generated users cannot disable multi-factor auth module\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"homeassistant/auth/__init__.py","lineNumber":434,"sourceCode":"        self, user: models.User, mfa_module_id: str, data: Any\n    ) -> None:\n        \"\"\"Enable a multi-factor auth module for user.\"\"\"\n        if user.system_generated:\n            raise ValueError(\n                \"System generated users cannot enable multi-factor auth module.\"\n            )\n\n        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:\n            raise ValueError(f\"Unable find multi-factor auth module: {mfa_module_id}\")\n\n        await module.async_setup_user(user.id, data)\n\n    async def async_disable_user_mfa(\n        self, user: models.User, mfa_module_id: str\n    ) -> None:\n        \"\"\"Disable a multi-factor auth module for user.\"\"\"\n        if user.system_generated:\n            raise ValueError(\n                \"System generated users cannot disable multi-factor auth module.\"\n            )\n\n        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:\n            raise ValueError(f\"Unable find multi-factor auth module: {mfa_module_id}\")\n\n        await module.async_depose_user(user.id)\n\n    async def async_get_enabled_mfa(self, user: models.User) -> dict[str, str]:\n        \"\"\"List enabled mfa modules for user.\"\"\"\n        modules: dict[str, str] = OrderedDict()\n        for module_id, module in self._mfa_modules.items():\n            if await module.async_is_user_setup(user.id):\n                modules[module_id] = module.name\n        return modules\n\n    async def async_create_refresh_token(\n        self,","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/auth/__init__.py#L416-L452","documentation":"ConfigEntryNotReady in bryant_evolution signals that the HVAC gateway (Evolution gateway accessed over a local serial/USB file) was not reachable at setup, so HA should retry later. It is raised two ways: explicitly when a probe via _can_reach_device(client) fails, and from FileNotFoundError when BryantEvolutionLocalClient.get_client cannot open the configured serial device file.","triggerScenarios":"Calling BryantEvolutionLocalClient.get_client(system, zone, entry.data[CONF_FILENAME]) raises FileNotFoundError because CONF_FILENAME (a tty/USB device path like /dev/ttyUSB0) does not exist; or the client is created but `await _can_reach_device(client)` returns falsy (no response from the Evolution system/zone), triggering the bare `raise ConfigEntryNotReady`.","commonSituations":"USB-to-serial adapter re-enumerated to a different /dev node (ttyUSB0 → ttyUSB1) after reboot or replug, udev permissions deny access to the tty (file absent for the HA user), the gateway/bryant bridge is powered off or its serial cable disconnected, or the configured system/zone numbers don't correspond to a reachable zone.","solutions":["Verify the device file in the config entry exists and is stable: `ls -l /dev/ttyUSB*` on the machine running HA; if the node changed, update CONF_FILENAME or add a udev rule to pin a stable symlink.","Check permissions: the Home Assistant process user needs rw access to the tty (dialout group on HassOS/Debian); test with `ls -l /dev/ttyUSB0` and fix group membership or the udev rule.","Confirm the Bryant/Carrier Evolution gateway is powered and connected (serial cable seated, zone numbers in CONF_SYSTEM_ZONE match the installed system/zone).","Do nothing for transient cases — HA retries setup with backoff; once the adapter/gateway comes back the entry sets up successfully."],"exampleFix":"// before\nclient = await BryantEvolutionLocalClient.get_client(\n    sz[0], sz[1], entry.data[CONF_FILENAME]\n)\nif not await _can_reach_device(client):\n    raise ConfigEntryNotReady\n// after (user-side): pin a stable device symlink via udev instead of raw ttyUSBx:\n# /etc/udev/rules.d/99-bryant.rules\n# SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"xxxx\", ATTRS{idProduct}==\"yyyy\", SYMLINK+=\"bryant_evolution\"\n# then configure CONF_FILENAME as /dev/bryant_evolution","handlingStrategy":"validation","validationCode":"# Before setup: verify the serial device node exists and is readable/writable\nimport os, pathlib\n\ndef serial_device_ok(filename: str) -> bool:\n    p = pathlib.Path(filename)\n    return p.exists() and os.access(p, os.R_OK | os.W_OK)","typeGuard":"def is_missing_device(err: BaseException) -> bool:\n    \"\"\"True when the local serial device path does not exist.\"\"\"\n    return isinstance(err, FileNotFoundError)","tryCatchPattern":"for sz in entry.data[CONF_SYSTEM_ZONE]:\n    try:\n        client = await BryantEvolutionLocalClient.get_client(sz[0], sz[1], entry.data[CONF_FILENAME])\n        if not await _can_reach_device(client):\n            raise ConfigEntryNotReady\n        entry.runtime_data[tuple(sz)] = client\n    except FileNotFoundError as f:\n        raise ConfigEntryNotReady from f  # retry when the tty reappears","preventionTips":["Use a udev rule with a stable SYMLINK (e.g. /dev/bryant_evolution) instead of /dev/ttyUSB0 so reboots/replugs don't change the path.","Ensure the HA process user has rw permission on the tty (dialout group) before setup.","Keep CONF_SYSTEM_ZONE values in sync with the actual Evolution system/zone addressing.","Expect ConfigEntryNotReady during adapter/gateway power loss — HA's backoff retries make it self-healing once hardware returns."],"tags":["home-assistant","config-entry","serial-device","bryant-evolution","file-not-found","hardware"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}