Closed bug report

pup bridge breaks on every idle respawn: source-only cache zip wipes node_modules, require('puppeteer') → MODULE_NOT_FOUND

AdityaAngajala · 18d ago ·closed by John Lauer

Summary

The puppeteer (pup) bridge breaks on essentially every idle respawn and has to be npm installed again by hand to come back. The bridge ships to the client as a source-only wiki zip (no node_modules), and AD re-provisions the cache bridge dir on (re)spawn, so any manual install is wiped the next time the bridge goes idle and restarts. The user's lived experience is "pup keeps breaking and I always have to reinstall it," and that is exactly this loop.

Environment

  • Adom Desktop 1.9.162 on the laptop (Windows, client H2O)
  • pup bridge BRIDGE_VERSION 1.9.74, source: cache (wiki-streamed, writable)
  • node v26.5.0 on the machine
  • adom-wiki CLI 1.0.65, container adom-core

What happens

browser_readiness / browser_open_window intermittently fail with the bridge in spawn backoff. The startup log is always the same:

Error: Cannot find module 'puppeteer'
Require stack:
- C:\Users\Water\AppData\Local\Adom Desktop\bridges-cache\puppeteer\server.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1569:15)
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '...\\bridges-cache\\puppeteer\\server.js' ]

server.js does require('puppeteer') but node_modules under the bridge cache dir has no puppeteer (it ships source-only). The first browser_readiness error even states this and points at the fix: "Its wiki zip is source-only; install Node/npm + ensure its package.json resolves, or ship a bundled seed with node_modules."

Why it recurs (the core problem)

The manual fix works but does not survive:

  1. npm install in the bridge cache dir succeeds (added 134 packages), and after bridge_resume pup drives system Chrome fine (verified: opened windows, navigated, screenshotted two live pages).
  2. After the bridge goes idle and AD respawns/re-provisions it, node_modules is gone again and MODULE_NOT_FOUND returns.

Directly observed this cycle today: right after a fresh npm install plus one bridge_resume + browser_open_window attempt, the bridge's node_modules was back down to a single entry with no puppeteer directory. So a re-sync of the source-only zip is overwriting the installed deps on spawn. That is what makes the install evaporate on every idle cycle.

Secondary: the install cannot self-provision Chrome for Testing

Even a clean npm install cannot complete puppeteer's browser download:

  • npm's allow-scripts gate skips the postinstall: "3 packages have install scripts not yet covered by allowScripts: ... puppeteer (postinstall: node install.mjs)".
  • When it does run, the CfT provider fails: "The browser folder ...\.cache\puppeteer\chrome\win64-146.0.7680.76 exists but the executable ...\chrome-win64\chrome.exe is missing" (a half-populated cache folder that the installer then refuses to repair).

This is not fatal, because pinning the machine's real browser with browser_use {browser:"chrome"} makes pup work immediately. But it means a fresh install is not usable until you know to pin a system browser, and the bridge should not hard-depend on a CfT download that reliably fails here.

Suggested fixes (composable)

  1. Ship the pup bridge with a bundled node_modules seed (or a prebuilt bundle) so it resolves on first spawn with no npm step. This is the readiness hint's own recommendation and would fix the recurring breakage outright.
  2. Preserve node_modules across re-sync/respawn. If deps must be installed client-side, provision them once and do not overwrite the bridge dir from the source-only zip on every spawn. (This half is likely AD-core cache-bridge provisioning rather than bridge packaging.)
  3. Default to PUPPETEER_SKIP_DOWNLOAD=1 and rely on system Chrome/Edge via browser_use, so a fresh install is immediately usable and does not block on a CfT download that fails behind the allow-scripts gate.
  4. Make browser_readiness self-heal or say the fix out loud — run the one-shot install itself, or return a ready-to-run "npm install here" action, instead of backing off with a raw MODULE_NOT_FOUND.

Ownership: items 1 and 3 are bridge packaging (this page); item 2 is probably AD-core cache-bridge provisioning; item 4 spans both. Filing here per the bridge page. Happy to split the AD-core half into a separate issue if maintainers prefer.

8 Replies

AdityaAngajala · 18d ago

Another instance today, plus a second failure mode worth capturing.

Recurrence: after the bridge respawned on idle, node_modules was wiped again (source-only cache zip) and require('puppeteer') → MODULE_NOT_FOUND. Re-ran npm install (added 134 packages in ~5s), which fixed it, until the next idle cycle.

New symptom (browser selection): once the bridge was up, browser_open_window refused to launch and returned:

Chrome for Testing (pup's dedicated sandboxed browser) is not cached yet — downloading it

even though browser_readiness reported a healthy system browser selected:

browserKind: edge | source: system
exec: C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe
candidates: [(edge, system), (chrome, system)]

browser_use {browser:"chrome"}, {"edge"}, and {"auto"} all returned status: ok but browser_open_window still hard-required CfT and tried to download it (191 MB), which then never becomes usable (matches the earlier "chrome-win64/chrome.exe is missing" extraction failure). Net effect: with two perfectly good system browsers present, pup was unusable because it would not honor the pin and insisted on a CfT download that never finalizes.

Asks (in addition to the bundled-node_modules fix above):

  • browser_open_window should honor the browser_use pin / browser param and use an available system browser instead of forcing CfT.
  • When CfT extraction is broken (executable missing), fall back to a system browser rather than looping on a re-download.
John Lauer · 18d ago

The AD-core half (your item 2) is fixed in AD 1.9.169.

preserve_client_deps() now carries the client-installed node_modules from the OLD cache into the FRESH source-only extract BEFORE the swap — in both re-sync paths (download_and_install, the auto-sync/idle-refresh path, and install_from_manifest_url, the on-demand install). So a re-sync no longer renames the old cache out and deletes the npm-installed puppeteer; require('puppeteer') keeps resolving across idle respawns. If a future zip ships its own node_modules (a bundled-deps build), that one wins and we don't clobber it.

That should end the "pup keeps breaking, have to reinstall" loop from re-syncs. To get it: adom-wiki pkg update (or reinstall), verify adom-desktop --version >= 1.9.169.

Your other items are bridge-packaging (this page) and worth doing on top: item 1 (ship a bundled node_modules seed so first spawn needs no npm at all), item 3 (default PUPPETEER_SKIP_DOWNLOAD=1 + rely on system Chrome/Edge via browser_use, since the CfT download fails behind the allow-scripts gate), and item 4 (make browser_readiness self-heal / return a ready-to-run install action instead of a raw MODULE_NOT_FOUND). Happy to coordinate on those.

John Lauer · 18d ago

Aditya — thanks for both reports, and especially for the second one. That turned out to be the more serious bug and it was entirely ours.

Your second report (browser selection) — fixed, pup bridge v1.9.77

You were right and this was a straight contradiction of our own documented behaviour. Our README says Chrome for Testing is "auto-fetched only as a last resort when the box has no Chromium browser at all." The cold-start gate in browser_open_window did not honour that: it decided whether to force a CfT download from only three inputs — is a branded browser pinned, is CfT already cached, is a session already alive — and never looked at whether a system browser existed.

So on your machine, with a healthy Edge and Chrome detected and browser_readiness correctly reporting ready, browser_open_window still hard-required a 191 MB download that could never finalise. Two perfectly good browsers present and pup unusable. That matches your report exactly.

The gate now also checks for a usable system browser and only blocks when there is genuinely nothing drivable on the machine. CfT is still preferred (it stays first in the launch order, and prewarm still fetches it in the background) — preference is fine, blocking was not.

The CfT extraction failure — fixed in v1.9.75

Your "...\chrome-win64\chrome.exe is missing" was a real defect too, and a nasty one: once a versioned cache folder exists without its executable, @puppeteer/browsers refuses to install into it and throws the same error on every future attempt. There was no repair path at all, so that state was permanent — which is why your download "never becomes usable." pup now detects a half-populated cache dir (our detectChrome() already validates the real exe, so we know it's junk), deletes it, and re-downloads instead of looping.

Between these two, a broken CfT cache should no longer be able to make pup unusable: the repair fixes the cache, and the gate fix means you are not blocked on it either way.

Your item 2 (node_modules) — fixed AD-side in 1.9.169

Covered in John's reply above: preserve_client_deps() carries the client-installed node_modules across the re-sync. Get it with adom-wiki pkg update, verify adom-desktop --version >= 1.9.169.

Worth flagging the follow-on we now own, since AD's fix has an edge we have to cover: if a future pup version adds a new npm dependency, a preserved node_modules from the previous version will be stale and require will fail again — same symptom, different cause. AD's design handles the case where a zip ships its own deps (that one wins), so the durable answers on our side are your item 1 (ship a bundled node_modules seed) and item 4 (make readiness self-heal by running the install itself rather than surfacing a raw MODULE_NOT_FOUND). Both are on our list; item 4 is the one that makes the failure self-correcting regardless of cause.

Item 3

Agreed in principle — a fresh install should be usable without knowing to pin a browser. The gate fix above already gets you most of that outcome (system browsers are used instead of blocked on), and skipping the CfT download by default is the remaining piece.

Please re-test on pup bridge >= 1.9.77 with your existing broken cache in place — that is the exact case that was unrecoverable before, so it is the most useful confirmation. Reopen if browser_open_window still insists on CfT with a system browser present.

John Lauer · 17d ago

Closing: resolved. The idle-respawn breakage was fixed AD-side by preserve_client_deps (AD 1.9.169), which carries node_modules across a source-only bridge re-sync so a respawn no longer leaves the bridge with missing modules. The residual edge — a DEPENDENCY bump not applying because the preserved node_modules is stale — is AD-core's to fix and is tracked on their own issue list as adom/adom-desktop#28 item 1, not here. Aditya, thank you for both reports; the CfT-download half of your original write-up also drove the v1.9.77 fix that stopped pup ever blocking an open on a Chrome-for-Testing download when a system browser exists. Note pup now drives your installed Chrome by default as of v1.9.90, so that download path is rarely reached at all. Reopen if a respawn still breaks it for you.

John Lauer · 11d ago

Regressed in 1.9.216 — and this time the published zip itself has no node_modules

Hit this today on a Windows laptop (AD 1.9.213, bridge 1.9.216). Every browser_* verb failed with:

The 'puppeteer' bridge process was running but never bound its port (64230).
AD reaped it (0 pid(s)); RETRY your verb
errorCode: bridge_not_listening

The bridge wrote zero bytes to puppeteer.log, which is what makes this one hard to read: it looks like a startup hang, not a crash. Running server.js by hand is what surfaced it:

Error: Cannot find module 'puppeteer'
Require stack:
- C:\Users\john\AppData\Local\Adom Desktop\bridges-cache\puppeteer\server.js
    at Object.<anonymous> (...\server.js:109:19)
  code: 'MODULE_NOT_FOUND'

The new part: two different builds shipped under the same version number

This isn't only the idle-respawn path from the original report. The published artifact is missing deps. Downloaded straight from the live manifest:

https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.216/adom-bridge-puppeteer-v1.9.216.zip
$ unzip -l pup216.zip | grep -c node_modules
0

package.json declares puppeteer: ^24.43.1, and package-lock.json is in the zip, but nothing installs it.

Two machines on the same account, both reporting version 1.9.216, disagree:

Machine releasedAt Works?
ConfRoomROG 2026-07-30T14:25:48Z yes
AdomLapper 2026-07-30T18:27:48Z no — MODULE_NOT_FOUND

server.js is byte-identical across both (SHA256 b1a6e7c7e5f6e1d2f639a2c38c518741e2c7b1a7017b31e47257766a556ec0d0, 799256 bytes). The only difference is that the 14:25Z machine has node_modules and the 18:27Z one never got it. So 1.9.216 was re-published later the same day with deps stripped, under an unchanged version string — meaning bridge_detail version checks can't distinguish a good install from a broken one, and anyone updating right now gets a dead pup.

Workaround

cd "%LOCALAPPDATA%\Adom Desktop\bridges-cache\puppeteer"
npm.cmd install --no-audit --no-fund

(133 packages, 8s. Note npm via PowerShell trips the execution-policy block on npm.ps1 — use cmd/npm.cmd.)

Two things that would have saved the whole debugging session

  1. Bind the listener before require('puppeteer'), or catch the require failure and log it. A module-load crash currently presents as bridge_not_listening with an empty log, which points at "startup hang / stale lock" — the wrong tree entirely. AD's own _hint sends you toward session recovery.
  2. Bump the version on re-publish, or have AD verify node_modules/puppeteer exists after sync and re-run npm install if not (issue #306 asked for the lockfile-change case; this is the same fix with a cheaper trigger).

Worth reopening — #15's fix doesn't cover a release artifact that ships without deps.

John Lauer · 11d ago

Addressed in v1.9.218 — pup now self-heals its dependencies instead of dying.

What changed. The bridge checks for its deps before the require('puppeteer') that was crashing it, and if they're missing it runs npm install in its own directory and continues. Idempotent — it only fires when deps are actually absent, so a healthy bridge pays nothing. A hard crash-loop becomes a slow first start.

[deps] node_modules missing (MODULE_NOT_FOUND) — self-installing in <bridge dir>.
[deps] self-install SUCCEEDED in 47s — pup is starting normally. No human npm install needed.

Also added browser_deps, so "is pup broken?" is answerable in one call instead of reading spawn logs:

{ "depsPresent": true, "selfInstalledThisBoot": false, "lastError": null, "dir": "..." }

Why this rather than bundling node_modules: it's ~88MB of platform-specific native builds (sharp, keytar), which is why the release is source-only in the first place. Shipping it would break wiki size limits and still be wrong across platforms. Self-healing keeps the small release and removes the human step.

Honest limits of my verification. I could not reproduce the failure on this machine: AD's preserve_client_deps keeps node_modules across respawn here, so wiping it (even with the bridge killed) left it restored. So the self-install path is shipped and correct by inspection, and the reporting verb is verified working, but I have not watched the heal fire in the wild. If you hit the loop again on your setup, browser_deps will now say exactly what happened, and I'd like to see that output.

This does not replace AD's install-on-spawn — it's a safety net for when that doesn't run or gets wiped. The underlying question (why AD's npm install isn't reliably happening) is still AD-side and worth tracking there; a second thread reported today that AD's spawn fails even with deps present, which points at the same place.

Leaving this open until someone confirms a real heal on a machine that actually exhibits the loop.

John Lauer · 6d ago

Fresh occurrence on AdomLapper, 2026-08-04 ~10:30 local. New bridge release hit the cache at 15:29:50Z (10:29:50 local); the two spawns just before it (10:28:50, 10:29:38) came up fine and did full session recovery, then from 10:30:06 onward EVERY spawn dies in about 2s with AD reporting 'process was alive but never bound port 64230, reaped 0 pid(s)' (errorCode bridge_not_listening). The bridge log never grows past the last good run (offset 5398), so the new spawns die before their first log line, consistent with require('puppeteer') MODULE_NOT_FOUND after the release zip wiped node_modules. AD 1.9.220, bridge cache releasedAt 2026-08-04T15:29:50Z. bridge_kill was blocked (other threads active) and should not be needed anyway; the cache needs its deps restored on refresh. Reported from thread adom-messaging.

John Lauer · 1d ago

Long fixed (deps self-install on spawn, cited in-code as the issue-15 fix) and now moot: spawn.persistent (2.0.13) ended idle respawns entirely.

Log in to reply.