home-assistant/core · error · ValueError

System generated users cannot disable multi-factor auth modu

Error message

System generated users cannot disable multi-factor auth module.

What it means

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.

Source

Thrown at homeassistant/auth/__init__.py:434

        self, user: models.User, mfa_module_id: str, data: Any
    ) -> None:
        """Enable a multi-factor auth module for user."""
        if user.system_generated:
            raise ValueError(
                "System generated users cannot enable multi-factor auth module."
            )

        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
            raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")

        await module.async_setup_user(user.id, data)

    async def async_disable_user_mfa(
        self, user: models.User, mfa_module_id: str
    ) -> None:
        """Disable a multi-factor auth module for user."""
        if user.system_generated:
            raise ValueError(
                "System generated users cannot disable multi-factor auth module."
            )

        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
            raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")

        await module.async_depose_user(user.id)

    async def async_get_enabled_mfa(self, user: models.User) -> dict[str, str]:
        """List enabled mfa modules for user."""
        modules: dict[str, str] = OrderedDict()
        for module_id, module in self._mfa_modules.items():
            if await module.async_is_user_setup(user.id):
                modules[module_id] = module.name
        return modules

    async def async_create_refresh_token(
        self,

View on GitHub (pinned to 58a3fdb3ea)

Solutions

  1. 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.
  2. 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.
  3. Confirm the Bryant/Carrier Evolution gateway is powered and connected (serial cable seated, zone numbers in CONF_SYSTEM_ZONE match the installed system/zone).
  4. Do nothing for transient cases — HA retries setup with backoff; once the adapter/gateway comes back the entry sets up successfully.

Example fix

// before
client = await BryantEvolutionLocalClient.get_client(
    sz[0], sz[1], entry.data[CONF_FILENAME]
)
if not await _can_reach_device(client):
    raise ConfigEntryNotReady
// after (user-side): pin a stable device symlink via udev instead of raw ttyUSBx:
# /etc/udev/rules.d/99-bryant.rules
# SUBSYSTEM=="tty", ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="yyyy", SYMLINK+="bryant_evolution"
# then configure CONF_FILENAME as /dev/bryant_evolution
Defensive patterns

Strategy: validation

Validate before calling

# Before setup: verify the serial device node exists and is readable/writable
import os, pathlib

def serial_device_ok(filename: str) -> bool:
    p = pathlib.Path(filename)
    return p.exists() and os.access(p, os.R_OK | os.W_OK)

Type guard

def is_missing_device(err: BaseException) -> bool:
    """True when the local serial device path does not exist."""
    return isinstance(err, FileNotFoundError)

Try / catch

for sz in entry.data[CONF_SYSTEM_ZONE]:
    try:
        client = await BryantEvolutionLocalClient.get_client(sz[0], sz[1], entry.data[CONF_FILENAME])
        if not await _can_reach_device(client):
            raise ConfigEntryNotReady
        entry.runtime_data[tuple(sz)] = client
    except FileNotFoundError as f:
        raise ConfigEntryNotReady from f  # retry when the tty reappears

Prevention

When it happens

Trigger: 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`.

Common situations: 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.

Related errors


AI-assisted analysis of home-assistant/core@58a3fdb3ea (2026-08-14). Data as JSON: /api/errors/03e0465a022f902d. Report an issue: GitHub.