home-assistant / home-assistant/iOS
external_auth=1 stripped together with its leading "?" when the path is persisted — corrupted URL on next launch
- Dominant language
- Swift
- Stars
- 2.4k
- Forks
- 520
- Avg merge
- 7h 27m
- Merged PRs (30d)
- 264
Description
**iOS device model, version and app version**
Model Name: iPhone 13 mini
Software Version: 26.6.2
App version: 2026.9.1 (2026.2985) — also occurred on 2026.9.0 (2026.2874)
**Home Assistant Core Version**
Core 2026.9.1 (Home Assistant OS on a Raspberry Pi 5)
**Describe the bug**
When the app is used on the external URL, `external_auth=1` is injected as the first query
parameter. If the app is then terminated and relaunched on the internal URL, the restored
path has `external_auth=1` removed together with the `?` that preceded it, instead of the
`&` that followed it. The remaining parameters keep their `&` separators and end up glued
directly onto the path:
```
last loaded (external): https:///config/dashboard?external_auth=1&more-info-entity-id=update.home_assistant_core_update&more-info-view=info
restored (internal): http:///config/dashboard&more-info-entity-id=update.home_assistant_core_update&more-info-view=info
^ should be "?"
```
With no `?`, the whole string is parsed as a single path segment. The corrupted value is
persisted, so every subsequent launch reproduces it.
Switching networks while the app is running is handled correctly — the `?` is preserved,
both with the app in the foreground and with it backgrounded and brought back. The defect
only appears on a cold launch after the app has been terminated.
**Severity depends on which panel the path points at:**
- On a Lovelace dashboard the damage is mild — the view is not found and the frontend falls
back to the first view of that dashboard, so the more-info dialog is silently lost but the
UI works.
- On `/config/...` there is no such fallback. `/config/dashboard&more-info-entity-id=...`
renders **nothing at all**: blank screen, no sidebar, no tab bar, no buttons, and
therefore no way to recover from inside the app. This is how I originally hit it, and
recovering required attaching a Mac and running `location.href = '...'` in the Web
Inspector.
The frontend itself is healthy in the blank state — it loads and connects, only the URL is
wrong. From the Web Inspector attached to the app's WKWebView:
```json
{
"url": "http:///config/dashboard&more-info-entity-id=update.home_assistant_core_update&more-info-view=info",
"root": true,
"bodyLen": 2621,
"bridge": true,
"hass": true,
"scripts": []
}
```
`root: true` (the `` element exists), `hass: true` (WebSocket and auth
succeeded), `bridge: true` (app bridge present).
**To Reproduce**
Requires a server with both an internal and an external URL configured. Verified with the Web Inspector attached to the app's
WKWebView at each step.
1. On the home Wi-Fi, navigate to a path with query parameters — opening a more-info dialog
is the easiest. `location.href` →
`http:///config/dashboard?more-info-entity-id=update.home_assistant_core_update&more-info-view=info`
2. Turn Wi-Fi off on the iPhone. The app switches to the external URL. `location.href` →
`https:///config/dashboard?external_auth=1&more-info-entity-id=update.home_assistant_core_update&more-info-view=info`
— still correct, `external_auth=1` injected as the first parameter.
3. **Force-quit the app.**
4. Turn Wi-Fi back on and launch the app. `location.href` →
`http:///config/dashboard&more-info-entity-id=update.home_assistant_core_update&more-info-view=info`
— the `?` is gone, and the screen is blank.
A cold launch is required. Both of these switch the base URL correctly and preserve the
`?`, so neither reproduces the bug:
- performing step 4 with the app in the foreground
- backgrounding the app instead of force-quitting it at step 3, then returning to it
The same sequence on a Lovelace path, e.g.
`/home/areas-?more-info-entity-id=sensor.x&more-info-view=info`, produces the same
corrupted separator, but the frontend falls back to the first view of the dashboard instead
of rendering nothing.
**Relevant log lines**
The corrupted value being restored on launch:
```
[WebViewController+URLLoading.swift:199] resolvedLoadURL(for:)
> restoring last path: /config/dashboard&more-info-entity-id=update.home_assistant_core_update&more-info-view=info
```
The same handling misbehaves in the other direction too. Once the stored path contains no
`?`, loading it over the external URL appends `?external_auth=1` at the *end* rather than
inserting it at the front — note this is also a different call site (`:153` not `:132`):
```
[WebViewController+ProtocolConformance.swift:153] load(request:)
> Requesting webView navigation to
https:///config/dashboard&more-info-entity-id=...&more-info-view=info?external_auth=1
```
**How I originally ran into it**
Worth recording because it explains why this can appear "out of nowhere" after a Core
update rather than during everyday use. I updated Core 2026.9.0 → 2026.9.1 at 11:43. At
12:59 the app reconnected, detected the version change and forced a reload, while I was
away from home:
```
12:59:18.972 [WebViewController+URLLoading.swift:49] serverVersionDidChange(_:)
> Resetting frontend cache for after server version change
12:59:18.984 [WebViewController+URLLoading.swift:218] resolvedLoadURL(for:)
> preserving current path on base URL change: /config/dashboard
```
I happened to have the more-info dialog for `update.home_assistant_core_update` open, which
supplied the query parameters, and the forced reload put that path onto the external URL.
Once I was back on the home network the restored path was corrupt, and every launch from
then on showed the blank screen. The specific entity is incidental.
**Expected behavior**
1. `external_auth` should be added and removed via proper URL component handling
(`URLComponents` / `queryItems`) rather than string manipulation, so the query string is
rebuilt correctly in both directions.
2. Independently: if a restored path does not resolve to a route, the app should fall back
to the default dashboard. A panel that renders nothing leaves the app with no on-screen
way out, which turns a cosmetic URL bug into an unusable app.
**Additional context**
Workaround, via the Web Inspector console attached to the app's WKWebView:
```js
location.href = 'http:///lovelace/0'
```
Possibly related: #5661 — a blank screen that survives a reinstall. A reinstall clears the
persisted path, so that report cannot share my exact sequence, but the same defect would
explain it for a user who reaches the app over a remote URL and regenerates the bad path
each run. That reporter also notes that selecting a different dashboard does not work,
which is another navigation the app constructs itself.
Possibly related: home-assistant/frontend#26754 — the default dashboard in the iOS app
reverting to Overview, reportedly when switching between internal and external URLs, with
the trigger unknown to the reporter. That is the mild form of this defect: a corrupted path
does not resolve to a view, so the frontend falls back. I saw exactly that behaviour in the
Lovelace variant above.
Contributor guide
Research direction
Start in WebViewController+URLLoading.swift at resolvedLoadURL(for:) around lines 132 and 199, then inspect WebViewController+ProtocolConformance.swift around line 153. Reproduce the cold-launch network switch with a query-bearing path and trace both internal and external URL transitions. Done means query separators and external_auth are preserved in both directions, with an appropriate fallback for an unrestorable route.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 64/100