home-assistant / home-assistant/iOS
App stuck permanently on "Unable to connect / Retrying" when a saved server's refresh token is invalidated
- Dominant language
- Swift
- Stars
- 2.4k
- Forks
- 520
- Avg merge
- 7h 27m
- Merged PRs (30d)
- 264
Description
**Summary**
tl;dr- - no re-auth, no reset (iOS has no escape; macOS only via full container wipe)
When a saved server's refresh token is invalidated server-side (session revoked from the user profile Security page, or an auth-store rollback wipes tokens), the app rejects the dead token with `invalid_grant`, marks the connection permanently failed, and loops forever on "Unable to connect to Home Assistant. Retrying in N seconds." It never falls back to a login prompt for the existing server and offers no way to remove or re-add it. On iOS this is unrecoverable (saved server survives delete-and-reinstall via the keychain; no add/remove-server on the error screen). On macOS the same fault occurs and the only recovery is a full container + keychain wipe.
**Related**
- #3304 (open since 2024-12-30) reports the identical log signature (`invalid_grant` -> `Refresh token is invalid` -> `bailing`) but is framed narrowly as "cannot connect to a deleted server via the Nabu Casa URL" and has had no traction. This report generalizes it (any server-side token invalidation), adds macOS reproduction, and documents the recovery.
- PR #4509 "Stop reconnects on permanent token fetch failure" (merged 2026-04-14) introduced the permanent-bail behavior seen here (`shouldDisconnectPermanently: true` -> "stopping websocket reconnects after fatal auth token fetch failure"). That change stops the reconnect loop but ships no re-auth or reset path, which is the gap this issue asks to close.
**Environment**
- App: Home Assistant Companion 2026.7.1 (build 2026.2346)
- Devices: iPhone18,1 (iOS 26.5.2) and iMac20,2 (macOS 26.5.2) - reproduced on both
- HA Core 2026.7.4 (HAOS 18.1), aiohttp 3.14.3, Python 3.14
- Remote access: Nabu Casa Cloud (remote UI online throughout)
**Steps to reproduce**
1. Set up the companion app against a server.
2. Invalidate that app's refresh token server-side (revoke the session under Settings > user > Security, or an auth-store event wipes tokens).
3. Open the app.
**Expected**
On `invalid_grant` for a saved server, drop to that server's login webview to mint a fresh token, or expose a control to remove/re-add the server.
**Actual** (from the macOS app log)
```
WebSocket: connecting using url: http://192.168.1.153:8123
WebSocket: connected ... "Server": "Python/3.14 aiohttp/3.14.3", "Upgrade": "websocket"
WebSocket: phase transition to auth
[Error] TokenManager > Token 8013087645581833658 is expired by 7687245 seconds
[Error] TokenManager > refreshToken() > refresh token got error:
serverError(statusCode: 400, errorCode: "invalid_grant")
ClientEventStore > Refresh token is invalid, notifying user
[Error] Environment > WebSocket: delegate failed to provide access token
TokenFetchFailure(... shouldDisconnectPermanently: true), bailing
HAAPI > stopping websocket reconnects after fatal auth token fetch failure
```
The network path is healthy (WebSocket opens, server replies `auth: required`). The only failure is the rejected refresh token, treated as fatal instead of prompting re-login.
**Platform difference**
- macOS: stuck the same way, but the menu bar exposes "add server" from the error screen. Recovery required wiping the app's Group Container + Container + keychain items; then adding the server and logging in minted a valid token and it connects and stays connected.
- iOS: the retry screen has no add/remove-server, no manual-URL entry, no settings access. Delete-and-reinstall does not help - the saved server restores from a keychain item that survives deletion (verified: true delete not offload, iCloud app-data removed, iCloud Keychain sync off, device restart). Only remaining reset is Erase All Content and Settings.
**What actually resolved it (macOS)**
1. Quit the app.
2. Remove `~/Library/Group Containers/group.io.robbie.homeassistant` and `~/Library/Containers/io.robbie.HomeAssistant`.
3. Delete the app's keychain items (`homeassistant_api`, and internet-password entries for the server host).
4. Relaunch, add the server, log in - fresh token, connected.
**Ruled out**
- Server healthy; Nabu Casa remote online; both LAN and cloud URLs return 200 and the browser loads the full live dashboard on the same devices.
- A second iPhone's companion app works on the same network/DNS.
- No VPN; Private Relay off; no Screen Time; NextDNS shows zero blocked queries, HA allowlisted.
- Location = Authorized Always; correct internal URL selected and connected.
**Docs gap**
troubleshooting > resetting assumes uninstall+reinstall yields a fresh app (false on iOS) and has no macOS section. troubleshooting > networking does not cover `invalid_grant` / expired tokens.
**Suggested fixes**
1. Pair the permanent-bail from PR #4509 with a recovery path: on `invalid_grant` for a saved server, present that server's login webview to re-authenticate instead of only disconnecting and looping on the error screen.
2. Always expose "manage/remove/add server" on the connection-error screen (all platforms).
3. On fresh install, run onboarding even when a keychain-persisted server exists, or add a "reset app / forget all servers" control.
Contributor guide
Research direction
Start by reading PR #4509 and tracing the invalid_grant path through TokenManager, ClientEventStore, and Environment. Compare the iOS and macOS connection-error screens and the troubleshooting reset documentation. Done means an invalidated saved token has a documented, usable recovery path without wiping containers, keychain data, or the device.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, macos, swift
- Domain
- authentication, desktop, mobile
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100