Skip to content

fix(electron): strip stale native modules from NEXT_DIST_DIR, not hardcoded .next - #8794

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.49from
NBN-N3:fix/electron-standalone-abi-cleanup-path
Jul 28, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.49from
NBN-N3:fix/electron-standalone-abi-cleanup-path

Conversation

@NBN-N3

@NBN-N3 NBN-N3 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes the packaging defect behind the recurring "Internal Server Error on every route" reports on the Electron desktop build.

Reported with full forensics in #8792. Supersedes the abandoned #7123 (closed unmerged, CONFLICTING).

The bug

prepare-electron-standalone.mjs strips stale native module copies after rebuilding better-sqlite3 for the Electron ABI, but the cleanup call hardcodes ".next":

const NEXT_DIST_DIR = process.env.NEXT_DIST_DIR || ".build/next";   // line 14
...
removeNativeModules(join(ELECTRON_STANDALONE_DIR, ".next", "node_modules"), [   // line 200
  "better-sqlite3",
  "keytar",
]);

The real distDir is .build/next. Since removeNativeModules() early-returns when the directory does not exist, the cleanup silently no-ops and the plain-Node-ABI copy produced by next build ships inside the installer.

The intent was already documented in the surrounding comment — only the path was wrong:

"also drop any stray Node-ABI better-sqlite3 under .next/node_modules so it cannot shadow the rebuilt one."

Runtime impact

The standalone server runs under ELECTRON_RUN_AS_NODE, so it needs the Electron ABI (148 for electron 43), but gets the Node one (137):

ERR_DLOPEN_FAILED
  -> "Nenhum driver SQLite disponível ... better-sqlite3 (falhou)"
  -> fallback to sql.js (WASM)
  -> "Database closed" reconnect loop, WASM memory never reclaimed
  -> "[DB] Out of memory while probing storage.sqlite"
  -> HTTP 500 on /, /api/health, /v1/models

Verified on a clean install of v3.8.48 (Windows 11, Electron 43.1.0). Auditing every .node in the package by require()-ing it inside the app's own Electron runtime showed exactly one mismatch:

<app>\.build\next\node_modules\better-sqlite3-90e2652d1716b047\...\better_sqlite3.node  FAILED  built_for_NMV=137
<app>\.build\next\node_modules\keytar-eb44cd511463a26b\...\keytar.node                  OK      built_for_NMV=148
<app>\.build\next\node_modules\wreq-js-3b69dd5e46bd26d3\...\wreq-js.win32-x64-msvc.node OK      built_for_NMV=148
<app>\node_modules\better-sqlite3\build\Release\better_sqlite3.node                     OK      built_for_NMV=148

Overwriting the bundled copy with the correctly-built one that already ships in the same package restores the app immediately: HTTP 200 within ~2s, zero OOM entries, real /v1/messages completions succeed.

Changes

  1. removeNativeModules(... NEXT_DIST_DIR ...) instead of the hardcoded ".next".
  2. assertNoStaleHashedNatives() — throws if any hashed native copies survive the cleanup.

The second change is the reason this PR is not a one-liner. The failure mode here is a silent no-op: a wrong baseDir produces a green build and a broken installer, and the mismatch only surfaces on a user machine after their DB grows enough that sql.js can no longer load it. That is why this has regressed at least twice:

ABI Issue
Apr 2026 127 vs 145 #1497
Jul 2026 137 vs 148 #7082, #7681, #8792

With the assertion, a wrong path fails the build instead of shipping.

Notes

  • No behaviour change when the cleanup already works — the assertion is a no-op on a correct build.
  • assertNoStaleHashedNatives() reuses existsSync / readdirSync, already imported in this file.
  • Syntax validated with node --check.
  • I could not run the full Electron packaging pipeline locally, so I would appreciate a CI run on this branch to confirm the build passes end to end.

Refs #7082, #7681, #1497, #8792.

removeNativeModules() was called with a hardcoded ".next" path while the actual
distDir is NEXT_DIST_DIR (".build/next" by default). Because the function
early-returns when the directory does not exist, the cleanup silently no-opped
and the plain-Node-ABI better-sqlite3 copy produced by `next build` survived
into the packaged app.

At runtime the standalone server runs under ELECTRON_RUN_AS_NODE, so it needs
the Electron ABI (148 for electron 43). Loading the ABI-137 copy fails with
ERR_DLOPEN_FAILED, the app falls back to the sql.js WASM driver, the connection
is closed and retried in a loop, WASM memory is never reclaimed and the process
OOMs -> HTTP 500 on every route.

Also adds assertNoStaleHashedNatives() so a wrong baseDir fails the build
instead of silently shipping a broken installer. This has regressed at least
twice (diegosouzapw#1497 with ABI 127 vs 145, diegosouzapw#7082/diegosouzapw#7681 with 137 vs 148).

Refs diegosouzapw#7082, diegosouzapw#7681, diegosouzapw#1497, diegosouzapw#8792. Supersedes the abandoned diegosouzapw#7123.
@NBN-N3
NBN-N3 requested a review from diegosouzapw as a code owner July 27, 2026 20:36
@diegosouzapw
diegosouzapw merged commit ff168ab into diegosouzapw:release/v3.8.49 Jul 28, 2026
3 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jul 28, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…iegosouzapw#8794)

removeNativeModules() was called with a hardcoded ".next" path while the actual
distDir is NEXT_DIST_DIR (".build/next" by default). Because the function
early-returns when the directory does not exist, the cleanup silently no-opped
and the plain-Node-ABI better-sqlite3 copy produced by `next build` survived
into the packaged app.

At runtime the standalone server runs under ELECTRON_RUN_AS_NODE, so it needs
the Electron ABI (148 for electron 43). Loading the ABI-137 copy fails with
ERR_DLOPEN_FAILED, the app falls back to the sql.js WASM driver, the connection
is closed and retried in a loop, WASM memory is never reclaimed and the process
OOMs -> HTTP 500 on every route.

Also adds assertNoStaleHashedNatives() so a wrong baseDir fails the build
instead of silently shipping a broken installer. This has regressed at least
twice (diegosouzapw#1497 with ABI 127 vs 145, diegosouzapw#7082/diegosouzapw#7681 with 137 vs 148).

Refs diegosouzapw#7082, diegosouzapw#7681, diegosouzapw#1497, diegosouzapw#8792. Supersedes the abandoned diegosouzapw#7123.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…iegosouzapw#8794)

removeNativeModules() was called with a hardcoded ".next" path while the actual
distDir is NEXT_DIST_DIR (".build/next" by default). Because the function
early-returns when the directory does not exist, the cleanup silently no-opped
and the plain-Node-ABI better-sqlite3 copy produced by `next build` survived
into the packaged app.

At runtime the standalone server runs under ELECTRON_RUN_AS_NODE, so it needs
the Electron ABI (148 for electron 43). Loading the ABI-137 copy fails with
ERR_DLOPEN_FAILED, the app falls back to the sql.js WASM driver, the connection
is closed and retried in a loop, WASM memory is never reclaimed and the process
OOMs -> HTTP 500 on every route.

Also adds assertNoStaleHashedNatives() so a wrong baseDir fails the build
instead of silently shipping a broken installer. This has regressed at least
twice (diegosouzapw#1497 with ABI 127 vs 145, diegosouzapw#7082/diegosouzapw#7681 with 137 vs 148).

Refs diegosouzapw#7082, diegosouzapw#7681, diegosouzapw#1497, diegosouzapw#8792. Supersedes the abandoned diegosouzapw#7123.
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