{"record":{"id":"2a521ec934b7081b","repo":"home-assistant/core","slug":"client-name-already-exists","errorCode":null,"errorMessage":"{client_name} already exists","messagePattern":"(.+?) already exists","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"homeassistant/auth/__init__.py","lineNumber":504,"sourceCode":"\n        if token_type == models.TOKEN_TYPE_NORMAL and client_id is None:\n            raise ValueError(\"Client is required to generate a refresh token.\")\n\n        if (\n            token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN\n            and client_name is None\n        ):\n            raise ValueError(\"Client_name is required for long-lived access token\")\n\n        if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN:\n            for token in user.refresh_tokens.values():\n                if (\n                    token.client_name == client_name\n                    and token.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN\n                ):\n                    # Each client_name can only have one\n                    # long_lived_access_token type of refresh token\n                    raise ValueError(f\"{client_name} already exists\")\n\n        return await self._store.async_create_refresh_token(\n            user,\n            client_id,\n            client_name,\n            client_icon,\n            token_type,\n            access_token_expiration,\n            expire_at,\n            credential,\n        )\n\n    @callback\n    def async_get_refresh_token(self, token_id: str) -> models.RefreshToken | None:\n        \"\"\"Get refresh token by id.\"\"\"\n        return self._store.async_get_refresh_token(token_id)\n\n    @callback","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/auth/__init__.py#L486-L522","documentation":"Thrown by AuthManager.async_create_refresh_token (homeassistant/auth/__init__.py:504) when creating a long-lived access token whose client_name already exists among the user's refresh tokens of the same type. Each client_name may back exactly one long-lived token per user, so duplicates are rejected.","triggerScenarios":"Creating a second long-lived token with a name already used by an existing token for that user; re-running a provisioning script that always uses the same name.","commonSituations":"Idempotency-unaware setup scripts; users creating 'API token' twice from tooling; name collisions after restoring a config backup while the script also creates the token.","solutions":["Reuse the existing token: search user.refresh_tokens for the client_name and return that token instead of creating a new one","Delete the old token first (hass.auth.async_remove_refresh_token) if rotation is intended","Use a unique name, e.g. append a timestamp or purpose suffix"],"exampleFix":"// before\nawait hass.auth.async_create_refresh_token(\n    user, token_type=models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, client_name=\"script\"\n)\n\n# after\nexisting = next(\n    (t for t in user.refresh_tokens.values()\n     if t.client_name == \"script\"\n     and t.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN),\n    None,\n)\nif existing is None:\n    await hass.auth.async_create_refresh_token(\n        user, token_type=models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, client_name=\"script\"\n    )","handlingStrategy":"validation","validationCode":"existing = next(\n    (t for t in user.refresh_tokens.values()\n     if t.client_name == client_name\n     and t.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN),\n    None,\n)\nif existing is None:\n    await hass.auth.async_create_refresh_token(\n        user, token_type=models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, client_name=client_name\n    )","typeGuard":"def name_is_free(user, client_name) -> bool:\n    return not any(\n        t.client_name == client_name\n        and t.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN\n        for t in user.refresh_tokens.values()\n    )","tryCatchPattern":"try:\n    token = await hass.auth.async_create_refresh_token(\n        user, token_type=models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, client_name=client_name\n    )\nexcept ValueError as err:\n    if \"already exists\" not in str(err):\n        raise\n    token = next(t for t in user.refresh_tokens.values() if t.client_name == client_name)","preventionTips":["Make token-provisioning scripts idempotent: look up by client_name before creating","Rotate by deleting the old token first when you want a fresh secret","Include a purpose or date suffix in generated client names"],"tags":["auth","python","home-assistant","long-lived-token","duplicate","valueerror"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}