home-assistant / home-assistant/iOS

App stuck permanently on "Unable to connect / Retrying" when a saved server's refresh token is invalidated

Open
#5,278 8 comments 0 reactions 0 assignees View on GitHub
bug ios
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.