Skip to content

fix(windows): externalize @ngrok/ngrok in Node server bundle#1013

Closed
scarson wants to merge 1 commit into
garrytan:mainfrom
scarson:fix/windows-node-bundle-ngrok-external
Closed

fix(windows): externalize @ngrok/ngrok in Node server bundle#1013
scarson wants to merge 1 commit into
garrytan:mainfrom
scarson:fix/windows-node-bundle-ngrok-external

Conversation

@scarson

@scarson scarson commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

The Windows Node-compat server bundle build (browse/scripts/build-node-server.sh) has been silently failing since v0.15.12 with:

error: cannot write multiple output files without an output directory

Root cause: @ngrok/ngrok (added in #815 for pair-agent tunneling) pulls in a ~10 MB native .node addon as a build asset. bun build --outfile refuses any build that emits more than one file, so the script exits 1, leaves the old server-node.mjs in place, and the rest of bun run build continues. macOS/Linux users don't notice because their browse binary runs the server directly under Bun — but on Windows, browse.exe spawns node server-node.mjs (Bun can't drive Playwright's Chromium on Windows: oven-sh/bun#4253, oven-sh/bun#9911), so a stale bundle is load-bearing.

Net effect on Windows: a current browse.exe talks to a v0.15.11-era server over HTTP after ./setup. Missing features:

  • v0.16.0 browser data platform (media, data, download, scrape, archive, network capture, GET /file)
  • v0.16.4 cookie origin pinning + command audit log
  • v0.17.0 ux-audit command + snapshot -H/--heatmap
  • various v0.15.12+ content-security envelopes for /pair-agent

Fix

One-line: externalize @ngrok/ngrok alongside the existing playwright/playwright-core/diff/bun:sqlite externals. Ngrok is used exclusively via dynamic import in server.ts:

const ngrok = await import('@ngrok/ngrok');   // line 1588 and 2368

…so runtime semantics are unchanged — Node resolves it from node_modules on first /pair-agent tunnel, which is identical to how playwright already works. Bundle drops back to a single file.

Test plan

  • bun install && bash browse/scripts/build-node-server.sh on a clean clone → builds successfully, emits only server-node.mjs
  • node --input-type=module -e "import('./server-node.mjs')" loads without errors and starts the HTTP listener
  • Grep confirms v0.17 features are present in the bundle (ux-audit, heatmap, content-security envelopes, 21 matches)
  • Full /pair-agent tunnel create (not tested — need a paying ngrok account; relying on the fact that playwright externalization uses the same mechanism)

🤖 Generated with Claude Code

The bundled native addon from @ngrok/ngrok emits a separate .node file
alongside the JS output, which makes bun build refuse --outfile with
"cannot write multiple output files without an output directory". The
build script has been silently broken on Windows since v0.15.12 when
ngrok was added for pair-agent tunneling (garrytan#815).

Windows users see fresh browse.exe/find-browse.exe after ./setup, but
server-node.mjs stops updating — producing a Frankenstein install where
a current CLI talks to a v0.15.11-era server over HTTP. Missing features:
browser data platform (v0.16.0), cookie origin pinning + audit log
(v0.16.4), ux-audit + snapshot heatmap (v0.17.0).

Fix: externalize @ngrok/ngrok the same way playwright and bun:sqlite are
already externalized. @ngrok/ngrok is used exclusively via dynamic import
(`await import('@ngrok/ngrok')` in server.ts) so runtime semantics are
unchanged — Node resolves it from node_modules on first pair-agent
tunnel. Bundle drops back to a single file.

Verified on clean clone: build succeeds, bundle loads under Node, v0.17
server features present.
@garrytan

Copy link
Copy Markdown
Owner

Closing — duplicate of several PRs fixing the same @ngrok/ngrok Windows build issue. We'll pick the best approach and merge separately. Thank you for the contribution!

@garrytan garrytan closed this Apr 16, 2026
garrytan added a commit that referenced this pull request Apr 16, 2026
… Windows

@ngrok/ngrok has a native .node addon that causes `bun build --outfile` to
fail with "cannot write multiple output files without an output directory".
Externalize it alongside the existing runtime deps (playwright, diff,
bun:sqlite), matching the exact pattern used for every other dynamic import
in server.ts.

Adds a policy comment explaining when to extend the externals list so the
next native dep doesn't repeat this failure.

Two community contributors independently converged on this fix:
 - @tomasmontbrun-hash (#1019)
 - @scarson (#1013)
Also fixes issues #1010 and #960.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
garrytan added a commit that referenced this pull request Apr 16, 2026
Fixes shipped in this version:
- Externalize @ngrok/ngrok so the Node server bundle builds on Windows
  (PRs #1019, #1013; issues #1010, #960)
- Shell precedence fix so build/test failures no longer exit 0 in CI
- Build validation test for server-node.mjs
- Windows setup verifies @ngrok/ngrok native binary is loadable

Credit: @tomasmontbrun-hash (#1019), @scarson (#1013).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan

Copy link
Copy Markdown
Owner

Thanks @scarson — landed your fix in #1024 (v0.18.0.1). Your diagnosis was particularly sharp: you caught that the bug had been shipping silently since v0.15.12 (which explained why Windows users were running a stale v0.15.11-era server bundle). That was the insight that motivated adding the root-cause fix — the || true in the package.json build command that let the build step fail silently in CI.

Closing in favor of #1024, which credits you in the CHANGELOG and commit body. Great work.

garrytan added a commit that referenced this pull request Apr 16, 2026
…1024)

* fix(browse): externalize @ngrok/ngrok so Node server bundle builds on Windows

@ngrok/ngrok has a native .node addon that causes `bun build --outfile` to
fail with "cannot write multiple output files without an output directory".
Externalize it alongside the existing runtime deps (playwright, diff,
bun:sqlite), matching the exact pattern used for every other dynamic import
in server.ts.

Adds a policy comment explaining when to extend the externals list so the
next native dep doesn't repeat this failure.

Two community contributors independently converged on this fix:
 - @tomasmontbrun-hash (#1019)
 - @scarson (#1013)
Also fixes issues #1010 and #960.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(package.json): subshell cleanup so || true stops masking build/test failures

Shell operator precedence trap in both the build and test scripts:

    cmd1 && cmd2 && ... && rm -f .*.bun-build || true
    bun test ... && bun run slop:diff 2>/dev/null || true

The trailing `|| true` was intended to suppress cleanup errors, but it
applies to the entire `&&` chain — so ANY failure (including the
build-node-server.sh failure that broke Windows installs since v0.15.12)
silently exits 0. CI ran the build, the build failed, and CI reported green.

Wrap the cleanup/slop-diff commands in subshells so `|| true` only scopes to
the intended step:

    ... && (rm -f .*.bun-build || true)
    bun test ... && (bun run slop:diff 2>/dev/null || true)

Verified: `bash -c 'false && echo A && rm -f X || true'` exits 0 (old,
broken), `bash -c 'false && echo A && (rm -f X || true)'` exits 1 (new,
correct).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(browse): add build validation test for server-node.mjs

Two assertions:
1. `node --check` passes on the built `server-node.mjs` (valid ES module
   syntax). This catches regressions where the post-processing steps (perl
   regex replacements) corrupt the bundle.
2. No inlined `@ngrok/ngrok` module identifiers (ngrok_napi, platform-
   specific binding packages). Verifies the --external flag actually kept
   it external.

Skips gracefully when `browse/dist/server-node.mjs` is missing — the dist
dir is gitignored, so a fresh clone + `bun test` without a prior build is
a valid state, not a failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(setup): verify @ngrok/ngrok can load on Windows

Mirror the existing Playwright verification step. Since @ngrok/ngrok is
now externalized in server-node.mjs (resolved at runtime from node_modules),
confirm the platform-specific native binary (@ngrok/ngrok-win32-x64-msvc et
al.) is installed at setup time rather than surfacing the failure later
when the user runs /pair-agent.

Same fallback pattern: if `node -e "require('@ngrok/ngrok')"` fails, fall
back to `npm install --no-save @ngrok/ngrok` to pull the missing binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump to v0.18.0.1 for ngrok Windows fix + CI error-propagation

Fixes shipped in this version:
- Externalize @ngrok/ngrok so the Node server bundle builds on Windows
  (PRs #1019, #1013; issues #1010, #960)
- Shell precedence fix so build/test failures no longer exit 0 in CI
- Build validation test for server-node.mjs
- Windows setup verifies @ngrok/ngrok native binary is loadable

Credit: @tomasmontbrun-hash (#1019), @scarson (#1013).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mathiasmora2232 pushed a commit to mathiasmora2232/gstack that referenced this pull request May 30, 2026
…arrytan#1024)

* fix(browse): externalize @ngrok/ngrok so Node server bundle builds on Windows

@ngrok/ngrok has a native .node addon that causes `bun build --outfile` to
fail with "cannot write multiple output files without an output directory".
Externalize it alongside the existing runtime deps (playwright, diff,
bun:sqlite), matching the exact pattern used for every other dynamic import
in server.ts.

Adds a policy comment explaining when to extend the externals list so the
next native dep doesn't repeat this failure.

Two community contributors independently converged on this fix:
 - @tomasmontbrun-hash (garrytan#1019)
 - @scarson (garrytan#1013)
Also fixes issues garrytan#1010 and garrytan#960.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(package.json): subshell cleanup so || true stops masking build/test failures

Shell operator precedence trap in both the build and test scripts:

    cmd1 && cmd2 && ... && rm -f .*.bun-build || true
    bun test ... && bun run slop:diff 2>/dev/null || true

The trailing `|| true` was intended to suppress cleanup errors, but it
applies to the entire `&&` chain — so ANY failure (including the
build-node-server.sh failure that broke Windows installs since v0.15.12)
silently exits 0. CI ran the build, the build failed, and CI reported green.

Wrap the cleanup/slop-diff commands in subshells so `|| true` only scopes to
the intended step:

    ... && (rm -f .*.bun-build || true)
    bun test ... && (bun run slop:diff 2>/dev/null || true)

Verified: `bash -c 'false && echo A && rm -f X || true'` exits 0 (old,
broken), `bash -c 'false && echo A && (rm -f X || true)'` exits 1 (new,
correct).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(browse): add build validation test for server-node.mjs

Two assertions:
1. `node --check` passes on the built `server-node.mjs` (valid ES module
   syntax). This catches regressions where the post-processing steps (perl
   regex replacements) corrupt the bundle.
2. No inlined `@ngrok/ngrok` module identifiers (ngrok_napi, platform-
   specific binding packages). Verifies the --external flag actually kept
   it external.

Skips gracefully when `browse/dist/server-node.mjs` is missing — the dist
dir is gitignored, so a fresh clone + `bun test` without a prior build is
a valid state, not a failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(setup): verify @ngrok/ngrok can load on Windows

Mirror the existing Playwright verification step. Since @ngrok/ngrok is
now externalized in server-node.mjs (resolved at runtime from node_modules),
confirm the platform-specific native binary (@ngrok/ngrok-win32-x64-msvc et
al.) is installed at setup time rather than surfacing the failure later
when the user runs /pair-agent.

Same fallback pattern: if `node -e "require('@ngrok/ngrok')"` fails, fall
back to `npm install --no-save @ngrok/ngrok` to pull the missing binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump to v0.18.0.1 for ngrok Windows fix + CI error-propagation

Fixes shipped in this version:
- Externalize @ngrok/ngrok so the Node server bundle builds on Windows
  (PRs garrytan#1019, garrytan#1013; issues garrytan#1010, garrytan#960)
- Shell precedence fix so build/test failures no longer exit 0 in CI
- Build validation test for server-node.mjs
- Windows setup verifies @ngrok/ngrok native binary is loadable

Credit: @tomasmontbrun-hash (garrytan#1019), @scarson (garrytan#1013).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants