Skip to content

Fix the manager crashing on launch - #802

Merged
JingMatrix merged 4 commits into
masterfrom
fix-manager-refactor-regressions
Jul 29, 2026
Merged

Fix the manager crashing on launch#802
JingMatrix merged 4 commits into
masterfrom
fix-manager-refactor-regressions

Conversation

@JingMatrix

@JingMatrix JingMatrix commented Jul 29, 2026

Copy link
Copy Markdown
Owner

OkHttp 5 ships its public suffix list as an asset and takes the Context needed to read it from an androidx.startup provider. The manager's manifest is never installed parasitically, so that provider never runs, and the first DNS lookup threw before opening a socket. HttpClientFactory now initialises OkHttp explicitly, as Coil already was. This is why installing the APK appeared to work around it: for a normal install the provider does run.

Two further defects turned that into a crash rather than a degraded Store, both fixed here:

  • VectorDns caught only UnknownHostException, so its documented fallback to the system resolver never engaged.
  • frameworkReleases and canaryBuilds called the network unguarded on viewModelScope.

Also in this branch:

  • Moves Coil's own initialisation from MainActivity to ServiceLocator.attach, for the same reason OkHttp's lives where the client is built: the debug demo host never opens MainActivity, so it had no image loader at all.
  • Discards crash records left by a different build. A record outlives the build that wrote it, so after an update the status card still shows the crash that was just fixed — which is what Crash when using action.sh to open shell manager #799 was answered with: five traces from the build before the one that fixed them.
  • Adds a status line under the DNS-over-HTTPS switch reporting what the resolver last did, with a retry when it has fallen back. The new strings need a Crowdin pass.

The GitHub sign-in removal that was on this branch earlier has been dropped from it, to keep this to the crash and what it turned up. It can come back on its own.

Verified on a Pixel 6: launch from the module action, forced fallback with the radios off, recovery without restarting the process, and a planted crash record from an older build discarded at the next launch while the current build's own record was kept.

Fixes #799.

OkHttp 5's Android artifact keeps the public suffix list in assets/ and
reaches it through a process-static Context that PlatformInitializer sets
from androidx.startup. Parasitically the manager's manifest is never
installed, so that provider never runs, and DnsOverHttps -- which asks the
list whether a host is private before it opens any socket -- died with
"Unable to load PublicSuffixDatabase.list" on the very first lookup. Coil
was already hand-initialised for this exact reason; OkHttp was not.

Initialise it where the one client is built rather than in an activity:
that is on the path to every request in both parasitic and standalone runs,
and in the debug demo host, which never opens MainActivity.

Two things then let a missing asset become a fatal instead of a degraded
feature, and both are fixed here:

- VectorDns caught only UnknownHostException, so an IllegalStateException
  escaped it, the latch never closed, and the fallback to the system
  resolver the class is built around never engaged.

- canaryBuilds and frameworkReleases called get() unguarded, unlike every
  other request in that file. Both are read from a viewModelScope or a
  LaunchedEffect, and there is no CoroutineExceptionHandler anywhere in the
  manager, so the throw landed on the main thread.

Verified on a Pixel 6: online the store loads with no DoH fallback; offline
with the disk cache cleared, DoH falls back once and the release fetch logs
"github release list unavailable" instead of taking the process down.
The switch said what DoH would do; nothing said what it was doing. Three
outcomes looked identical from the sheet -- the setting off, a proxy taking
the decision away, and a lookup that failed and latched the session onto the
system resolver -- and the last one existed only as a log line, on the very
networks the setting exists for.

VectorDns now records what each lookup did and the sheet renders it under
the switch. Observed, never probed: a "test DoH" button would be a second
code path, another host at another moment, and it can pass while the client
that fetches the module list is failing.

The fallback state also offers a way back. The latch is what keeps a blocked
endpoint from costing five seconds a name, but the session it belongs to is
com.android.shell -- a process nobody can restart on purpose -- so one bad
lookup on a captive portal disabled DoH until something else killed the
host.

Two things this turned up, fixed here:

- `direct` was a lazy val, so the proxy check ran once per process. Joining a
  VPN mid-session was invisible and DoH kept querying Cloudflare when the
  file says it should stand aside. It is read per lookup now, like the
  setting beside it.

- The failure line named the host being resolved, which is whichever request
  happened to be in flight -- and UnknownHostException carries that name as
  its entire message, so it printed twice while the useful part, which way it
  failed, went missing. What failed is reaching Cloudflare, so that is what
  it says, with the exception type as the detail.

Verified on a Pixel 6 in one process: resolved through Cloudflare, forced to
fall back with the radios off, recovered by pressing the action, and back to
resolving through Cloudflare with no second fallback.
A record outlives the build that wrote it: the directory is this process's
cache, which an update does not clear. So after installing a fix the status
card still shows the crash that was just fixed, and it stays there until
somebody thinks to press clear.

That is not a stale line on a screen. It is what a reporter copies into the
issue to show the fix did not work -- #799 was answered with five traces from
the build before the one that fixed them, which reads as a recurrence and is
not one.

Matched on the whole build line rather than the hash, because a build from a
dirty tree carries the hash of the commit it was built from. Only at install,
never at record: this is about what a new build inherits, and a crash loop
within one build must keep every record it makes. A file whose second line
cannot be read is kept -- losing a trace is the worse of the two failures.
Coil is initialised by hand for the same reason OkHttp now is: parasitically
this app's manifest is never installed, so nothing that self-registers there
ever runs. But it was done in MainActivity, and the debug demo host never
opens MainActivity -- which is the argument that moved OkHttp's initialisation
out of the activity in the first place, not followed through for the loader
beside it. The demo host therefore had no image loader at all, and every
avatar in it failed to load.

ServiceLocator.attach is where both entry points already meet. The factory is
not called until the first image, so this costs nothing at startup and does
not build the OkHttp client before something asks for it.
@JingMatrix
JingMatrix force-pushed the fix-manager-refactor-regressions branch from 6bdcb82 to 70a43cd Compare July 29, 2026 13:32
@JingMatrix
JingMatrix merged commit 3d8090f into master Jul 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash when using action.sh to open shell manager

1 participant