Closed bug report

Native host exits silently on version skew (host 0.1.2 vs bridge 0.1.49); bridge.json expectedExtensionVersion stale at 0.1.3

Drew Owens · 17d ago ·closed by John Lauer

Summary

On a machine that was working as recently as 2026-07-20, the native-browser extension stopped connecting after Adom Desktop auto-synced the bridge forward to 0.1.49. The service worker is installed and enabled but never establishes the native-messaging channel. The only user-visible signal is Chrome's cryptic "Unchecked runtime.lastError: Native host has exited." (screenshot below).

Root cause is a version skew that fails silently: this machine runs a stale 0.1.2 extension/host against bridge 0.1.49, and the bridge's own bridge.json advertises expectedExtensionVersion: 0.1.3 while the actually-shipping extension is 0.4.14. So even the built-in skew check is comparing against the wrong number and can never tell the user to update. On top of that, the host exits with code 0 (no message) and stale singleton PID files appear to block a fresh host.

This is bridge-author territory (the native-messaging host code, bridge.json, and the nbrowser_ diagnostics), so filing here rather than against adom-desktop.

Environment

  • Machine: adom_drew2 (Windows), user drew
  • Chrome: 150.0.7871.129, profile chrome:[email protected] (Default)
  • Adom Desktop: recent self-update around 2026-07-22 (bridge log shows a rotated bridge token: AD rejected our bridge token (403) ... AD likely self-updated and rotated it)
  • Bridge installed: native-browser 0.1.49 (.bridge-version = 0.1.49, bridge.json version = 0.1.49). bridge_check_updates reports current 0.1.49 = latest, updateAvailable: false.
  • Extension/host installed (unpacked): C:\Users\drew\adom-browser-extension\extension\manifest.json → name "Adom for Chrome & Edge", version 0.1.2, MV3. The host code (host\native-host.js, native-host.bat, host-manifest.json) is from the same 0.1.x checkout.
  • Latest on the wiki: extension line is at 0.4.14 (published 2026-07-23), bridge line at 0.1.49 (2026-07-22).

Symptom

chrome://extensions shows the Adom card Enabled with an Errors button. The error detail reads:

Unchecked runtime.lastError: Native host has exited.

nbrowser_statusnative browser extension not connected. nbrowser_wake_profile {profile:"chrome:[email protected]"}woke:false with the diagnosis "installed AND enabled ... but its service worker has not dialed the bridge." The bridge log never records a host connection after the reload (no hello: profile ... registered line).

Repro

  1. Install/enable the 0.1.2 extension unpacked in a Chrome profile (host code from the same checkout).
  2. Let Adom Desktop auto-sync the bridge forward to 0.1.49 (or refresh_bridges native-browser).
  3. Register the native host and reload the extension.
  4. Observe: chrome://extensions → Adom card → Errors → "Native host has exited"; nbrowser_status stays "not connected"; bridge log shows no host hello.

Root-cause analysis

The 0.1.2 host is protocol-incompatible with the 0.1.49 bridge: the host process launches, fails to complete the handshake/singleton, and calls process.exit(0), which Chrome surfaces as "Native host has exited." Three compounding, bridge-side defects turn a routine version skew into an hour-long dead end:

  1. bridge.json expectedExtensionVersion is stale. It reads 0.1.3, but the shipping extension is 0.4.14. Any skew detection that compares the connected extension version against this field is comparing against the wrong baseline, so it can never say "you're on 0.1.2, update to 0.4.14." (The page publishing two interleaved version lines — bridge 0.1.x and extension 0.4.x — is itself a confusion worth resolving or documenting.)

  2. The host exits silently on skew. native-host.js has several process.exit(0) paths with no user-visible reason:

    • acquireSingleton (line ~63) exits if another host holds the profile's singleton lock.
    • the reconnect policy gives up after max backoff (lines ~311/315) and exits. None of these emit a "your extension is out of date, update to X" signal; the user only ever sees Chrome's generic "Native host has exited."
  3. Stale singleton lock not reclaimed. The bridge dir had two stale lock files — host.nohello-67004.pid and [email protected] — referencing dead PIDs. A fresh host appears to exit rather than steal a dead-PID lock. (Deleting them + restarting the bridge did not by itself restore the connection, so skew is the primary cause, but the stale lock is a second foot-gun.)

Timeline supporting "regression, not fresh-install": bridge-debug.log shows successful hello: profile chrome:[email protected] registered on 2026-07-17 and 2026-07-20. Bridge releases 0.1.42–0.1.49 were published 2026-07-22. So the bridge moved forward under a host that stayed at 0.1.2.

Possibly-related older crash (may already be fixed in 0.1.49): the 2026-07-17 debug log has unhandledRejection: TypeError: (payload.closed || []).map is not a function at onHostFrame (server.js:1636:91) from an earlier bridge. Worth confirming the payload.closed guard exists on all onHostFrame paths in 0.1.49.

What I already fixed on this machine (not the bug)

The Chrome native-messaging host key was missing (removed at some point, likely during an AD update). I re-created HKCU\Software\Google\Chrome\NativeMessagingHosts\inc.adom.native_browser...\host\host-manifest.json. That changed the error from "native host not found" to "native host has exited", isolating the remaining failure to the host-exit / skew issue above.

Suggested fixes (asks)

  1. Bump bridge.json expectedExtensionVersion to the real current extension (0.4.14), and keep it in lockstep on future extension releases. Resolve or document the two-version-line scheme (bridge 0.1.x vs extension 0.4.x).
  2. Fail loud on skew. When the connected host/extension is older than the bridge expects, surface a clear "extension out of date, update to <version>" through the bridge and the nbrowser_ verbs, instead of exit(0) → "Native host has exited."
  3. Self-heal stale singleton locks. If host.*.pid points at a dead PID, reclaim the lock rather than exiting.
  4. Surface diagnosis in verbs. nbrowser_readiness / nbrowser_wake_profile should report extensionStale (installed vs expected) and the host-exit reason, so this is one call rather than manual spelunking through bridge-debug.log, host.json, and chrome://extensions errors.
  5. Confirm the (payload.closed || []).map guard is present on all onHostFrame paths in 0.1.49.

Attached

chrome://extensions error detail showing "Native host has exited" for the Adom extension.

2026-07-24T15-42-08.730-7799018

1 Reply

John Lauer · 17d ago

Thorough report - thank you. Worked through all five asks; three led to shipped fixes, and two of your root-cause points were based on a version-number mixup that is itself worth fixing. Bridge 0.1.50 is published and live.

The version-number confusion (the crux of asks #1 and the 'two lines' note)

There are actually THREE independent version streams, and the report conflated two of them:

stream current where
bridge runtime (bridge.json version) 0.1.50 Download card
extension + host (extension/manifest.json) 0.1.3 chrome://extensions
skills package (root package.json) 0.4.x Install card

So expectedExtensionVersion: 0.1.3 is NOT stale - it is correct: the shipping extension MV3 version IS 0.1.3. The '0.4.14' you read as the extension version is the skills-PACKAGE version, a different stream. Your machine's extension is genuinely 0.1.2, i.e. one behind the current 0.1.3 - so the skew is real, and expectedExtensionVersion is comparing against the right baseline. The check just never got to run (see #2). I've added a Three version numbers table to the README so nobody hits this again.

#2 + #4 - fail loud on skew, surface it in a verb (FIXED, bridge 0.1.50, auto-deploys)

You nailed the chicken-and-egg: the old extensionStale check only compared CONNECTED profiles, but a skew bad enough to break the native-messaging handshake means the extension never connects, so a connection-based check can never fire. Fixed: nbrowser_readiness now ALSO reads each known profile's extension version straight off disk (Secure Preferences) and reports:

  • staleOnDisk: [{profile, installed, expected}] - installed-but-too-old-to-connect, and
  • hostExit - the native host's own last exit reason (see below), plus a loud _hint: '⛔ VERSION SKEW (likely the cause of "Native host has exited"): [email protected] has extension 0.1.2, but this bridge (0.1.50) expects extension 0.1.3 ... FIX: update the extension, then nbrowser_dev_reload.' On your box that turns the whole spelunk into one nbrowser_readiness call.

Also on the host side: it now writes host-exit-reason.json next to the discovery file on every deliberate exit (singleton loss, backoff give-up), because Chrome only ever shows the user the generic 'Native host has exited.' readiness surfaces that reason. (This lands when the extension/host folders are redeployed - see below.)

#3 - stale singleton lock (already fixed in the CURRENT host; your 0.1.2 predates it)

The current host (0.1.3) already reclaims a dead-PID lock: acquireSingleton only defers if isLiveHost(pid) is true, and that verifies the PID is both alive AND actually a node process, otherwise it steals the lock. Your 0.1.2 host is from before that logic, which is why the stale host.*.pid files blocked it. The host.nohello-<pid>.pid file is per-PID so it can't block a different host. Updating the host to 0.1.3 resolves it.

#5 - payload.closed guard (CONFIRMED present)

The (payload.closed || []).map(...) guard IS in 0.1.50 (server.js, in the close-window path). Your unhandledRejection at server.js:1636 was from the 2026-07-17 bridge, well before the 0.1.42-0.1.50 line. Not reproducible on current.

Your actual fix on adom_drew2

The primary cause is the skew: update the extension + host from 0.1.2 to 0.1.3 (re-sync the extension/ and host/ folders to the current build per installing-the-extension), then reload the extension. That both clears the handshake incompatibility and brings in the dead-PID-lock reclaim + exit-reason logging. Re-creating the missing NativeMessagingHosts registry key (which you already did) was the right call to get past 'native host not found.'

Net: the mystery is now a one-call diagnosis for the next person (readiness staleOnDisk/hostExit), the version scheme is documented, and the host fails with a recorded reason instead of a silent exit(0). Closing - reopen if updating to 0.1.3 does not restore the connection on your box.

Log in to reply.