{"record":{"id":"129a41399caea23a","repo":"home-assistant/core","slug":"credential-is-already-linked-to-a-user","errorCode":null,"errorMessage":"Credential is already linked to a user","messagePattern":"Credential is already linked to a user","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"homeassistant/auth/__init__.py","lineNumber":341,"sourceCode":"            name=info.name,\n            is_active=info.is_active,\n            group_ids=[GROUP_ID_ADMIN if info.group is None else info.group],\n            local_only=info.local_only,\n        )\n\n        self.hass.bus.async_fire(EVENT_USER_ADDED, {\"user_id\": user.id})\n\n        return user\n\n    async def async_link_user(\n        self, user: models.User, credentials: models.Credentials\n    ) -> None:\n        \"\"\"Link credentials to an existing user.\"\"\"\n        linked_user = await self.async_get_user_by_credentials(credentials)\n        if linked_user == user:\n            return\n        if linked_user is not None:\n            raise ValueError(\"Credential is already linked to a user\")\n\n        await self._store.async_link_user(user, credentials)\n\n    async def async_remove_user(self, user: models.User) -> None:\n        \"\"\"Remove a user.\"\"\"\n        tasks = [\n            self.async_remove_credentials(credentials)\n            for credentials in user.credentials\n        ]\n\n        if tasks:\n            await asyncio.gather(*tasks)\n\n        await self._store.async_remove_user(user)\n\n        self.hass.bus.async_fire(EVENT_USER_REMOVED, {\"user_id\": user.id})\n\n    async def async_update_user(","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/auth/__init__.py#L323-L359","documentation":"ConfigEntryNotReady is Home Assistant's signal that a config entry could not be set up because a dependency (here the Bosch Smart Home Controller) was unreachable. In bosch_shc it is raised from SHCConnectionError while constructing the SHCSession, telling the config entry machinery to retry setup later instead of failing permanently. The coordinator pattern keeps the entry in a 'setup in progress/retry' state with exponential backoff.","triggerScenarios":"Creating an SHCSession (hass.async_add_executor_job(SHCSession, host, ssl_cert, ssl_key, False, zeroconf)) throws SHCConnectionError — the HTTP/WebSocket connection to the Bosch SHC box at data[CONF_HOST] cannot be established (device offline, wrong IP, TLS handshake failure, or mDNS/zeroconf info stale).","commonSituations":"Controller powered off or rebooting (e.g. after a firmware update), router DHCP changed the SHC IP while the config still pins the old host, self-signed certificate/key mismatch after re-pairing, or firewall/VLAN blocking port 8444/8084 to the controller.","solutions":["Verify the Bosch Smart Home Controller is powered on and reachable: ping the host / open https://<host>:8444 in a browser from the same machine running HA.","Check the configured host in the config entry still matches the controller's current IP (or relies on freshly discovered zeroconf info); re-run discovery or update the host.","Confirm the SSL certificate and key files referenced by CONF_SSL_CERTIFICATE/CONF_SSL_KEY still exist and match the controller's current cert (re-pair via the Bosch app if needed).","If the error persists, enable debug logging for bosch_shc and check the underlying SHCConnectionError message; HA will keep retrying with backoff, so a transient outage self-heals once connectivity returns."],"exampleFix":"// No code fix — this is a setup-time connectivity signal. But ensure transient\n// errors surface as NotReady rather than a hard failure:\ntry:\n    session = await hass.async_add_executor_job(\n        SHCSession, data[CONF_HOST], data[CONF_SSL_CERTIFICATE],\n        data[CONF_SSL_KEY], False, zeroconf,\n    )\nexcept SHCConnectionError as err:\n    raise ConfigEntryNotReady from err  # HA retries setup automatically\n// If you control the caller and the host may change, refresh it first:\n// entry.async_update_hass_config(hass, {**entry.data, CONF_HOST: new_ip})","handlingStrategy":"retry","validationCode":"# Before setup, probe reachability of the controller\nimport socket, ssl\n\ndef host_reachable(host: str, port: int = 8444, timeout: float = 3.0) -> bool:\n    try:\n        with socket.create_connection((host, port), timeout=timeout):\n            return True\n    except OSError:\n        return False","typeGuard":"# Not applicable — Python duck-typed integration; guard on exception type instead\nassert isinstance(err, SHCConnectionError)  # narrow before converting to ConfigEntryNotReady","tryCatchPattern":"try:\n    session = await hass.async_add_executor_job(SHCSession, host, cert, key, False, zeroconf)\nexcept SHCAuthenticationError as err:\n    raise ConfigEntryAuthFailed from err   # credential problem -> reauth\nexcept SHCConnectionError as err:\n    raise ConfigEntryNotReady from err      # transient -> retry with backoff","preventionTips":["Use a DHCP reservation or mDNS/zeroconf-discovered host so the controller IP never drifts.","Keep the pairing certificate and key files backed up; a mismatch after re-pairing causes persistent connection failures.","Watch for the 'UPDATE_AVAILABLE' warning in the source — update the controller via the Bosch app before debugging connectivity.","Alert on repeated ConfigEntryNotReady retries rather than ignoring them; sustained retries mean a real outage, not transient flakiness."],"tags":["home-assistant","config-entry","network","bosch-shc","connection-error"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}