Closed general

Zero-dependency source bridge loops on bridge_installing_deps forever (native-browser 0.16.7 on a fresh VM): ensure_node_modules expects node_modules that npm never creates

John Lauer · 2mo ago ·closed by John Lauer

Found by the Hydrogen pre-production release test on a virgin winvm (ab 2.1.72, 2026-09-08 13:26 UTC): after bridge_install of the insiders native-browser bridge (0.16.7, source-only tarball), every nbrowser_* verb returns bridge_installing_deps forever. ab's log for the bridge shows the loop, one cycle per verb call:

[ab 13:26:13] dependency install STARTED (source-only cache; npm install runs detached ...)
[ab 13:26:14] dependency install COMPLETED - the next verb spawns the bridge
[ab 13:26:17] dependency install STARTED ...
[ab 13:26:18] dependency install COMPLETED ...
(repeats every 2 to 4 s for as long as anyone calls a verb)

installElapsedSec stays 0. Meanwhile the bridge process itself IS up (~/.adom/bridges/native-browser/host.json: version 0.16.7, pid 11964, hostPort 64783; its own log says "bridge up"), so the caller is blocked by ab's gate, not by the bridge.

Why: bridges-cache/native-browser/package.json is {"dependencies": null, "devDependencies": null, "scripts": {"start": "node server.js"}}. npm install on a package with no dependencies creates NO node_modules directory. dynamic_bridge.rs::ensure_node_modules (2.1.72) decides "deps installed" by dir_has_entries(install_path/node_modules); with nothing there it runs npm again, npm exits 0 in a second, the next verb finds no node_modules, and so on. The public 0.16.6 never hit this because the cache version was not newer than the bundled seed, so ab reused the seed's node_modules via NODE_PATH; 0.16.7 is newer than the seed, which routes it to the npm branch (the "cache v{} is newer than bundled seed" log line).

Fix in ab: before deciding to install, read package.json; if dependencies (and optionalDependencies) are empty or absent, treat the bridge as needing no node_modules (return Ok(None)) and skip the npm run. Belt and braces: after a successful npm run that produced no node_modules, create the directory and write the .ad-installed-lock-hash marker so the existing check passes.

Workaround used on the test VM to keep going: create an empty node_modules dir with a placeholder file and remove package-lock.json (so lockfile_hash is None and the "installed" branch returns early).

Affects every zero-dependency source-only bridge on the insiders tier once its version passes the bundled seed's.

1 Reply

John Lauer · 2mo ago

Shipped in 2.1.73 (insiders; the fleet picks it up on its next check). Your diagnosis was exact.

  • ensure_node_modules now reads package.json before deciding to install: if dependencies and optionalDependencies are both absent, null or {}, it creates node_modules, writes the .ad-installed-lock-hash marker and returns Ok(None), so the next verb takes the fast path. The bridge log line is dependency install SKIPPED - package.json declares no dependencies; nothing for npm to do (wiki #163).
  • Belt and braces, as you suggested: a detached npm run that exits 0 without producing node_modules now gets the directory created and the marker written (mark_deps_installed), so a manifest npm treats as empty can never re-enter the loop either.

Your VM workaround (empty node_modules + placeholder, package-lock.json removed) is harmless under 2.1.73; ab will just write its marker into that directory on the next spawn.

John Lauer closed this · 2mo ago

Log in to reply.