Mpegger
(DannP)
September 12, 2026, 5:44pm
1
I don’t really know what’s going on, but recently a few times on my android devices, opening the HomeAssistant app opens up my primary web browser instead as if I’m trying to access the HA server http site through the web browser. It never actually connects, and I have to manually close out the web browser and open the HomeAssitant app again. It only started happening recently (within the last 2-3 weeks), but very infrequently.
I do not know the inner working of Android, but it seems to occur when the Android device was asleep for quite a number of hours, and I would also assume that the HomeAssistant app was unloaded from ram.
Has anyone else experienced this? Is it a bug or known issue?
Seems to be. Fix is already in the companion app beta apparently.
opened 02:14PM - 04 Sep 26 UTC
closed 08:08AM - 07 Sep 26 UTC
bug
# Companion app ejects to external browser on cold start (2026.6.5)
**Status:**… unresolved, not filed upstream as of 2026-09-04. Ready to file.
**Reported by:** David · **Device:** Galaxy S25 Ultra (SM-S938U), Android 16 / SDK 36
**App:** 2026.6.5-full · **WebView:** com.google.android.webview 153.0.8010.27
**Server:** HA OS, Core 2026.9.0 · internal `http://192.168.2.5:8123`, external Nabu Casa
---
## Symptom
Cold-starting the companion app (after terminating it) opens Home Assistant in the
external browser (Brave) at `http://192.168.2.5:8123/?external_auth=1` instead of
rendering in-app. The page hangs on:
> Could not load Home Assistant. Please refresh the page… Unable to connect to
> Home Assistant. Retrying in NN seconds…
Closing the browser tab and relaunching the app works normally. Fully repeatable.
The hang in the browser is expected, not a second bug: `external_auth=1` tells the
frontend to wait for the native app's auth bridge, which never answers outside the
WebView. The real defect is the ejection itself.
## Root cause (confirmed from logs)
Companion app log, cold start at 09:44:32.302:
```
09-04 09:44:32.302 15885 15885 D WebViewActivity$onCreate: Launching browser
```
That string appears in exactly one place — `shouldOverrideUrlLoading` in
`app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt`
(tag 2026.6.5, ~line 656):
```kotlin
} else if (!webView.url.toString().contains(it)) {
Timber.d("Launching browser")
val browserIntent = Intent(Intent.ACTION_VIEW, it.toUri())
startActivity(browserIntent)
return true
}
```
A navigation target is compared against the WebView's current URL with a naive
`String.contains()` substring test. When it fails, the app fires `ACTION_VIEW` and
the default browser takes over. The ejected URL carries `?external_auth=1`, which is
appended by the app itself — so the app is throwing its *own* URL out to the browser.
## Preceding log context
```
09-04 09:44:29.111 I HomeAssistantApplication: Running 2026.6.5-full on SDK 36
09-04 09:44:29.185 D HomeAssistantApplication$configureWebViewDebugging: Current webview package com.google.android.webview and version 153.0.8010.27
09-04 09:44:31.812 D WebViewActivity$onCreate: Matter/Thread step changed to NOT_STARTED
09-04 09:44:31.914 W ServerManagerImpl: Activate with SERVER_ID_ACTIVE is not doing anything
09-04 09:44:31.937 D WebViewActivity: Loading HIDDEN (keepHistory true, openInApp true, serverHandleInsets false)
09-04 09:44:31.943 D WebViewActivity: Waiting for loadedUrl HIDDEN
09-04 09:44:32.137 E WebViewActivity$onCreate: onReceivedHttpError: 404 : Not Found for: http://192.168.2.5:8123/local/card-mod.js
09-04 09:44:32.302 D WebViewActivity$onCreate: Launching browser
09-04 09:44:32.326 D WebViewActivity: External bus id=1 type=config/get
09-04 09:44:32.523 D WebViewActivity: External bus id=2 type=connection-status
09-04 09:44:33.271 D WebViewActivity: External bus id=4 type=frontend/loaded
```
On a later relaunch in the same log, the guard passes and no ejection occurs:
```
09-04 09:44:38.732 D WebViewActivity: Loading HIDDEN (keepHistory true, openInApp true, serverHandleInsets false)
09-04 09:44:38.732 D WebViewActivity: Same base URL without meaningful path, skipping load
```
## Ruled out
| Hypothesis | Test | Result |
|---|---|---|
| Stale/broken Android System WebView | Updated, verified enabled, checked WebView implementation | Not it — 153.0.8010.27 loads fine |
| Brave claiming links via app-link defaults | Cleared defaults for Brave and HA ("Set as default" on One UI) | Not it |
| Custom dashboard cards / Lovelace resources (35 registered) | Created blank dashboard, set as user Default dashboard, cold start | Still ejects |
| Internal URL / http / bare-IP origin | Blanked internal URL, forced Nabu Casa HTTPS, cold start | Still ejects |
| App out of date | 2026.6.5 is current stable (2026.6.6 is pre-release only) | No update available |
| Stale WebView URL from prior session | Parked app on dashboard root before terminating; also ran "Reset frontend cache" | Still ejects |
Server-side is healthy: Core 2026.9.0, state RUNNING, reachable from browsers.
## Separate, unrelated defect found in the same log
`http://192.168.2.5:8123/local/card-mod.js` returns 404. No dashboard resource
registers that path — card-mod is registered at
`/hacsfiles/lovelace-card-mod/card-mod.js`. The only modules served from `/local/`
are the two hand-patched ones:
- `/local/yarbo-card.js?v=3`
- `/local/power-gauge-flow-card.js?v=9`
One of them almost certainly contains a relative `import … from "./card-mod.js"`,
which resolves to `/local/card-mod.js`. Fix: change it to the absolute
`/hacsfiles/lovelace-card-mod/card-mod.js`. Worth doing regardless — a failing module
import leaves that card partially initialized. **This is not the cause of the
ejection** (proven by the blank-dashboard test), just a neighbor in the timeline.
## Prior art on the tracker
| Issue | State | Relation |
|---|---|---|
| [android#5074](https://github.com/home-assistant/android/issues/5074) — Automation opens in browser instead of app | Closed | Same mechanism, different trigger (clicking an automation link). Fixed in **frontend** (PR home-assistant/frontend#24780), not the app. Best precedent. |
| [android#6375](https://github.com/home-assistant/android/issues/6375) — Authentication fails when opened in external browser | Closed, not planned | External browser + auth, but reverse-proxy auth scenario |
| [android#3193](https://github.com/home-assistant/android/issues/3193) — Dashboard sometimes fails to load when opening application | Closed, not planned | Samsung, intermittent launch failure, no browser ejection, no logs |
No open or closed issue matches a cold-start browser ejection with a
`Launching browser` log line.
## Remaining unknown
The log does not print the navigation target that fails the `contains()` check. The
one step that would identify it is USB debugging via `chrome://inspect` on a desktop
(WebView debugging is already enabled — see `configureWebViewDebugging` above),
watching the Network tab with "Preserve log" during a cold start. Not yet done.
---
## Ready-to-paste issue body
> **Title:** App ejects to external browser on cold start — `shouldOverrideUrlLoading` `contains()` check rejects the app's own URL
**Home Assistant Android version:** 2026.6.5-full
**Android version:** 16 (SDK 36)
**Device:** Samsung Galaxy S25 Ultra (SM-S938U)
**Android System WebView:** com.google.android.webview 153.0.8010.27
**Home Assistant version:** Core 2026.9.0 (HA OS)
### Describe the bug
On every cold start (app fully terminated, then launched), the companion app opens
Home Assistant in my external browser instead of its own WebView. The browser loads
`http://192.168.2.5:8123/?external_auth=1` and hangs on "Could not load Home
Assistant… Unable to connect… Retrying in NN seconds" — expected, since
`external_auth=1` requires the native auth bridge that isn't present outside the app.
If I close the browser tab and relaunch the app, it loads normally in-app. 100%
repeatable.
### Diagnosis
The companion log shows the app doing this itself:
```
09-04 09:44:31.937 D WebViewActivity: Loading HIDDEN (keepHistory true, openInApp true, serverHandleInsets false)
09-04 09:44:31.943 D WebViewActivity: Waiting for loadedUrl HIDDEN
09-04 09:44:32.302 D WebViewActivity$onCreate: Launching browser
```
`Launching browser` comes from `shouldOverrideUrlLoading` in `WebViewActivity.kt`:
```kotlin
} else if (!webView.url.toString().contains(it)) {
Timber.d("Launching browser")
val browserIntent = Intent(Intent.ACTION_VIEW, it.toUri())
startActivity(browserIntent)
return true
}
```
The ejected URL carries `?external_auth=1`, which the app appends itself — so a URL
the app generated is being classified as external by its own guard and handed to the
browser. The `contains()` substring test looks like the problem; an origin comparison
would presumably be correct here.
This appears to be the same class of bug as #5074, which was fixed frontend-side in
home-assistant/frontend#24780.
### Things I have already ruled out
- Android System WebView is current and enabled; WebView implementation verified
- Cleared link/app defaults for both the browser and the HA app
- Not dashboard content: with a blank dashboard set as my user's Default dashboard,
it still ejects (I run 35 custom Lovelace resources, all bypassed by this test)
- Not the URL or origin: happens over Nabu Casa HTTPS as well as the internal
`http://192.168.2.5:8123`
- "Reset frontend cache" and Android-level Clear cache both performed
- 2026.6.5 is current stable
- Server is healthy and loads fine in desktop browsers
### Timeline
Started after updating Core to 2026.9.0; app version has not changed.
opened 01:01PM - 03 Sep 26 UTC
closed 08:06AM - 07 Sep 26 UTC
### Describe the bug
When the frontend's stale-build recovery (Home Assistant C… ore 2026.9.0, frontend 20260826.4) reloads the page with `location.replace(<same URL>?ha_cache_bust=<timestamp>)`, the Android app hands that navigation to the **system browser** instead of loading it in its own WebView. Chrome opens on top of the app at the server's URL, and because Chrome has no session it lands on the Home Assistant login page. With a permanently-failing import (my case, see below) this happens on every single app launch. Even with the frontend fix in [home-assistant/frontend#53940](https://github.com/home-assistant/frontend/pull/53940) the recovery still fires legitimately after some updates, so the app will still bounce to Chrome then. Bram asked for this to be filed against the app.
### To reproduce
1. Core 2026.9.0. Register a Lovelace resource (or `frontend: extra_module_url` entry) pointing at a file that does not exist, e.g. `/local/missing.js`.
2. Open the app. The dashboard loads and authenticates normally.
3. Within a few seconds the frontend detects the failed import, wipes caches and navigates to `https://<server>/?external_auth=1&ha_cache_bust=1788437722060`.
4. Chrome opens with that URL and shows the HA login page.
Server access log for one launch (the phone is 192.168.23.45; note the user agents):
```
13:15:22.082 GET /?external_auth=1 200 "Mozilla/5.0 (Linux; Android 16; SM-S938B ...; wv) ... Home Assistant/2026.6.5-22884 (Android 16; SM-S938B)"
13:15:22.741 GET /?external_auth=1&ha_cache_bust=1788437722060 200 "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 ... Chrome/151.0.0.0 Mobile Safari/537.36"
```
First line is the app's WebView loading the frontend. Second line, 0.7 s later, is the same URL plus `ha_cache_bust`, fetched by Chrome itself (no `wv`, no app suffix). The URL is same-origin, same path, only a query parameter added. In a desktop browser the same navigation simply reloads the page.
### Expected behavior
A `location.replace` / navigation to the app's own server origin should stay inside the WebView regardless of query parameters, so the frontend's recovery completes in-app (the frontend then strips `ha_cache_bust` on the next successful connect). Only genuinely foreign origins should go to the external browser.
### Environment
- Companion app 2026.6.5-full (22884), Android 16, Samsung SM-S938B
- Core 2026.9.0, HAOS on Proxmox, connected over the internal URL (https, port 8123)
- Not yet tested on 2026.8.x (FrontendScreen); happy to update and retest if that helps
### Additional context
Root cause on my side was a dead `extra_module_url` file; fixed by removing it, and the core-side 31-day caching of the 404 is filed as a separate core issue. The app behaviour is independent of both: any navigation with `ha_cache_bust` in the query goes to Chrome.