Skip to content

fix(electron): make the packaged Windows build pass the #7592 cold-restart smoke - #11443

Merged
diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.51from
jonlwheat2-gif:fix/7592-windows-packaged-smoke
Aug 25, 2026
Merged

diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.51from
jonlwheat2-gif:fix/7592-windows-packaged-smoke

Conversation

@jonlwheat2-gif

@jonlwheat2-gif jonlwheat2-gif commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

#7592's root-cause fixes (#6605 Electron/ABI rebuild, #7353 hashed-external normalization, #6835 retry cap) and regression guard (#10921 cold-restart + assertNativeDriverSelected) had never run against a real packaged Windows build — the issue stayed open for exactly that validation. This PR is that validation, plus the five defects it surfaced and fixes. Final result on a fresh release/v3.8.50 clone, fully built and packaged on Windows:

[electron-smoke] ready: http://127.0.0.1:20128/login returned HTTP 200      (launch 1, fresh DATA_DIR)
[electron-smoke] cold-restart: relaunching against the same DATA_DIR
[electron-smoke] ready: http://127.0.0.1:20128/login returned HTTP 200      (launch 2, same DATA_DIR)
[electron-smoke] cold-restart: native SQLite driver confirmed on second launch

Findings — what broke, what happened, what fixed it

1. Optional-pack staging failed on Windows (GNU tar remote-host parsing)

  • What happened: npm run build:win died with optional-pack tar failed for optional-pack-ml-runtime.tar.gz (exit 2) — twice, so not transient. The thrown error discarded tar's stderr, hiding the cause.
  • Diagnosis: manual tar succeeded; spawnSync("tar.exe", ["-czf", "C:\...tar.gz", ...]) failed. Surfacing stderr: tar (child): Cannot connect to C: resolve failed — GNU tar (Git-for-Windows /usr/bin) parses the drive letter in an absolute -f C:\... path as an rsh remote host. bsdtar (System32, what CI runners resolve) tolerates colons, so CI never saw this.
  • Fix: tarPack() (scripts/build/optionalPackStaging.mjs) now passes a bare filename with cwd at the tarball directory — portable across GNU tar and bsdtar — and the thrown error includes tar stderr.

2. Packaged main process crashed at startup: missing lib/loginHeaderCapture.js

  • What happened: first packaged launch → error dialog Cannot find module './lib/loginHeaderCapture', require stack loginManager.js ← main.js.
  • Diagnosis: loginManager.js:15 requires it top-level (fatal), but the asar files allowlist in electron/package.json omitted it.
  • Fix: added lib/loginHeaderCapture.js to files.

3. electron-builder ≥26 silently dropped node_modules from extraResources — the native driver never shipped

  • What happened: after (2), the packaged app still had no native SQLite driver: resources/app/node_modules was empty while the staging tree (.build/electron-standalone/node_modules) held 103 modules including better-sqlite3 with its prebuilds/win32-x64.node. A diff of staging vs packed output showed exactly one missing top-level entry: node_modules.
  • Diagnosis: app-builder-lib/out/fileMatcher.js injects !**/node_modules/** into every extraResources/extraFiles pattern list, and no later positive pattern can override it — proven with a minimal fixture on 26.15.3: builds clean, control files copied, node_modules dropped under all three filter orderings (["**/*","node_modules/**/*"], reversed, and with the dir itself listed).
  • Consequence: every fresh v3.8.50 Windows desktop build would ship without the native driver and reproduce fix(startup): better-sqlite3 not unpacked from app.asar → native module load fails → sql.js fallback OOM crash #7592's sql.js fallback on every machine — the exact regression the smoke guard exists to catch, guaranteed-failing before the guard even ran.
  • Fix: new scripts/build/afterpack-copy-node-modules.mjs, wired as afterPack in electron/package.json, restoring the staged node_modules into resources/app post-pack (103 modules, verified prebuilds/win32-x64.node present in the package).

4. Smoke harness: redirected USERPROFILE broke Electron userData → single-instance lock false → silent exit(0)

  • What happened: the packaged app exited code=0 ~2s after launch, before serving; the smoke reported exited before readiness. No captured logs — which turned out to be meaningless: a Windows GUI Electron app's console.log never reaches a captured stdout pipe, so "no logs" was never evidence. (First proven by an asar-instrumented run: the process was alive and logging to a file while the captured pipe stayed empty.)
  • Diagnosis chain (file-based logging patched into the asar):
    • app.getPath("userData") → throws Failed to get 'userData' path
    • requestSingleInstanceLock() (which resolves userData internally) → false → main.js:58-59 does app.quit(); process.exit(0); — the only silent, pre-whenReady exit path in the main process (confirmed by graph-wide grep of exit paths).
    • Bisect of the redirected env vars: real APPDATA + redirected LOCALAPPDATA → OK; redirected LOCALAPPDATA only → OK; redirected USERPROFILE alone → reproduces. Electron derives the Roaming profile from %USERPROFILE%\AppData\Roaming\<name> — USERPROFILE takes precedence over the APPDATA env var — and the path service throws rather than creates the missing directory. The harness pre-created APPDATA\{omniroute-desktop,OmniRoute,omniroute} but never the USERPROFILE-derived tree.
    • Control: pre-creating %USERPROFILE%\AppData\Roaming\omniroute-desktop flips gotTheLock=true and whenReady is reached.
  • Fix: ensureSmokeEnvDirs (scripts/dev/smoke-electron-packaged.mjs) now also pre-creates the USERPROFILE-derived tree; exported for unit testing.

5. The #7592 driver assertion was unreachable — no [DB] Driver: line on the server's primary DB path

  • What happened: with the app finally surviving both launches, the smoke failed its final assertion: logs contain no '[DB] Driver: ...' line.
  • Diagnosis: the line is emitted only by openDatabaseAsync() (driverFactory.ts:371), whose only production caller is the db-backups import route. Server startup opens the DB via getDbInstance() (core.ts:1286 → logs [DB] SQLite database ready without naming the driver). The guard asserted a line the product never prints on this path.
  • Fix: getDbInstance() now logs [DB] Driver: ${db.driver} | file: ${sqliteFile} right after its primary open — same format the guard's NATIVE_DRIVER_LOG_PATTERN / SQLJS_DRIVER_LOG_PATTERN parse, so a sql.js fallback now fails the assertion loudly.

Environmental (documented, no repo change needed)

  • RDP sessions: the GPU process crashes 6× → FATAL: GPU process isn't usable. Goodbye. → abort. The smoke already passes --no-sandbox --disable-gpu when CI is set — run it with CI=1 outside GitHub Actions too.
  • First-launch migrations: 158 migrations exceed the 45s default readiness window on real hardware; ELECTRON_SMOKE_TIMEOUT_MS=180000 covers it (cold-restart second launch is fast).

Test plan

  • node --import tsx/esm --test tests/unit/electron-smoke-script.test.ts → 9 pass / 0 fail on Windows (previously 6/7: the env-allowlist test hardcoded POSIX paths and could only pass on Linux/macOS — expectations are now built with path.join; plus two new regression tests: USERPROFILE-derived Roaming tree pre-creation, and tarPack under absolute Windows-style paths).

  • Full packaged validation (Hard Rule fix(ci): add environment for npm token access #18 — real-environment record):

    # fresh clone of release/v3.8.50 (8bbe92c69) — the validation host SHA; branch since rebased onto release/v3.8.51 — then:
    npm ci
    npm run build
    npm run prepare:bundle --prefix electron
    npx electron-builder --win --dir
    CI=1 ELECTRON_SMOKE_COLD_RESTART=1 ELECTRON_SMOKE_TIMEOUT_MS=180000 \
      node scripts/dev/smoke-electron-packaged.mjs
    # → launch 1 HTTP 200 → cold restart → launch 2 HTTP 200
    # → "cold-restart: native SQLite driver confirmed on second launch"
    

Notes for reviewers

Review follow-ups (this branch)

  • Rebased onto release/v3.8.51 (d82b68274) carrying only the electron-smoke work: cherry-pick of 9dc7711d was clean exactly as predicted, plus one follow-up commit.
  • Cross-platform guard fix: ensureSmokeEnvDirs branched its win32 USERPROFILE/APPDATA userData-tree creation on the host os.platform(), so the fix(startup): better-sqlite3 not unpacked from app.asar → native module load fails → sql.js fallback OOM crash #7592 regression test ran 8/9 on Linux CI. The function now takes { currentPlatform } (same injection style as stopApp/buildSmokeEnv; default remains the host platform so real packaged-smoke runs are unchanged) and the regression test injects "win32". Suite: 9/9.
  • Stale references removed from this description (old base-red note, "targets v3.8.50" note).

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.50 to release/v3.8.51 August 25, 2026 00:42
@diegosouzapw

Copy link
Copy Markdown
Owner

Outstanding debugging write-up — the USERPROFILE-over-APPDATA userData chain and the electron-builder !/node_modules/ finding are exactly the kind of root causes worth documenting, and the Hard Rule #18 packaged-Windows validation record in the body is appreciated. Two blockers before this can move: (1) your branch carries ~82 unrelated commits cut from release/v3.8.50 that are not yet on release/v3.8.51 — merged as-is it would drag that whole line into v3.8.51 out-of-band; please rebase onto release/v3.8.51 carrying only the electron-smoke commit (verified: all touched files exist there unchanged, cherry-pick is clean). (2) The new USERPROFILE regression test fails on Linux — executed here, the suite is 8/9 because ensureSmokeEnvDirs branches on host os.platform(), not the injected currentPlatform, so the win32 tree is never created on a Linux CI host and test:unit goes red. Parameterize the platform (or derive it from the smoke env) so the guard is testable cross-platform, and re-run the suite. Minor: the body's base-red reference (#9985) and the 'targets release/v3.8.50' note are stale now that the PR sits on release/v3.8.51.

@Xxx91n

Xxx91n commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

@diegosouzapw
This PR fixes the black screen on Windows Electron 3.8.50 in my environment. After installing the new build include this PR the app launches and the server starts without module errors. Runtime modules are present and the database is ready. The problem is resolved.
The relevant changes from this PR must land on release/v3.8.50 before the v3.8.50 release is cut, per the packaged Windows validation and release freeze.

@jonlwheat2-gif
jonlwheat2-gif force-pushed the fix/7592-windows-packaged-smoke branch from 9dc7711 to 70f7ef3 Compare August 25, 2026 12:54
@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

Both blockers are cleared on the pushed branch (9dc7711d cherry-pick + one follow-up, on release/v3.8.51 @ d82b68274):

(1) Rebase — done exactly as you verified: cherry-pick of the electron-smoke commit onto .51 applied clean; the branch now carries only that work (2 commits total: 7e1ee76b5 smoke + 70f7ef363 below). No v3.8.50-line commits ride along.

(2) Linux-red USERPROFILE test — root cause confirmed as you diagnosed: ensureSmokeEnvDirs branched its win32 userData-tree creation on the host os.platform(), so on Linux the injected win32-shaped env never got its tree and the guard ran 8/9. Fixed by parameterizing { currentPlatform = platform() } — same injection convention stopApp/buildSmokeEnv already use in that file — with the host default left intact for real packaged-smoke runs, and the regression test now injects "win32" explicitly. Suite is 9/9 locally.

(3) Stale body references — removed the old base-red note and the "targets release/v3.8.50" line; description now states the validation was executed on a v3.8.50-tip build while this PR targets release/v3.8.51.

@Xxx91n — thanks for confirming the packaged build resolves your black screen. Per the freeze (#11439) the fix lands via release/v3.8.51 (this PR's base); it will ride the next release cut rather than patching .50 in place.

…7592 cold-restart smoke

Five defects found while validating diegosouzapw#7592 on a real packaged Windows build:

- optionalPackStaging: GNU tar reads the drive letter in an absolute
  -f C:\... archive path as a remote rsh host (Cannot connect to C:),
  failing optional-pack staging on Git-for-Windows machines. Pass a
  bare filename with cwd at the tarball directory; surface tar stderr.
- electron/package.json: lib/loginHeaderCapture.js was missing from the
  asar files allowlist; loginManager.js requires it top-level, so the
  packaged main process crashed on launch.
- electron-builder >=26 injects !**/node_modules/** into every
  extraResources pattern list and no positive filter can override it,
  silently dropping the staged runtime node_modules (including the
  better-sqlite3 N-API prebuild) from resources/app. Add an afterPack
  hook (scripts/build/afterpack-copy-node-modules.mjs) that restores it.
- smoke harness: Electron resolves userData from
  %USERPROFILE%/AppData/Roaming/<name> (USERPROFILE wins over APPDATA)
  and the path service throws when it is missing, so
  requestSingleInstanceLock() returned false and the app exited(0)
  silently before whenReady. ensureSmokeEnvDirs now pre-creates the
  derived tree and is exported for tests.
- core.ts: the diegosouzapw#7592 guard parses a [DB] Driver: ... line that only the
  unused openDatabaseAsync() emitted; getDbInstance() now logs the same
  line on its primary open so the assertion is reachable.

Also makes the smoke env-allowlist unit test host-agnostic (it
hardcoded POSIX paths) and adds regression tests for the USERPROFILE
derived tree and tarPack under Windows-style absolute paths.

Closes diegosouzapw#7592

(cherry picked from commit 9dc7711)
ensureSmokeEnvDirs branched its win32 USERPROFILE/APPDATA userData-tree
creation on the HOST os.platform(), so the diegosouzapw#7592 regression guard could
never pass on a Linux CI host (suite ran 8/9 there). Parameterize
currentPlatform like stopApp/buildSmokeEnv already do (default stays host
platform() so the real packaged-smoke run is unchanged) and inject
'win32' from the regression test. 9/9 locally.
CI on the retargeted head surfaced failures that exist on the .51 tip
(a179ffe) independent of the smoke work:
- glmCodingProviderConfig fixtures missing glm-5.3-max inventory + routed
  tier (registry gained it; vitest 2 failed -> 10/10 after fix)
- DB migrations doc drift: code has 160, README/AGENTS/llm.txt + 42 i18n
  mirrors said 159
- config/quality/eslint-suppressions.json carried 10 stale entries ->
  lint:json exit 2; pruned against this exact tree
@jonlwheat2-gif
jonlwheat2-gif force-pushed the fix/7592-windows-packaged-smoke branch from 70f7ef3 to a134280 Compare August 25, 2026 13:15
@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

Follow-up on the retarget: the .51 base itself moved under us (tip d82b68274 → a179ffed5, 9 commits / 127 files, incl. #11493), and the first CI cycle on the rebased branch surfaced three failures that are base-red on that tip — none from the smoke work. Fixed them here so this PR is green standalone:

  1. Vitest — glmCodingProviderConfig (2 failed): registry gained glm-5.3-max, fixtures hadn't. Added to the inventory array + routed tiers (["glm-5.3-max", ["max"]]). 10/10 locally.
  2. Docs Gates: code has 160 DB migrations at this tip while README/AGENTS/llm.txt + the 42 docs/i18n/*/llm.txt mirrors said 159 → refreshed everywhere; check-docs-counts-sync exit 0 against provider count 353.
  3. No new ESLint warnings (exit 2): stale entries in config/quality/eslint-suppressions.json relative to the new tip sources → pruned against this exact tree.

Branch now = tip a179ffed5 + electron-smoke commit + cross-platform guard fix + these three gate alignments. Heads-up for whoever reviews #11450: it was measured against d82b68274; before merging it should re-measure docs counts against a179ffed5 too (provider count moved 352 → 353 with the new tip commits).

@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

Remaining reds on this PR — all inherited from the .51 tip, none introduced here

The second CI cycle still shows reds after my three gate alignments. Classification with evidence — every failing name matches a defect that exists on release/v3.8.51 @ a179ffed5 independent of this branch (our 4 commits touch only: smoke script + its test, GLM vitest fixtures, docs counts, suppressions prune):

Failing job / test Inherited cause at tip Covered by
Unit 1/4 · agent.json/agent-card ×5 getBaseUrl() dereferences request.nextUrl (src/lib/wellKnown.ts:10) when route handlers are invoked without a request #11450
Unit 1/4 · APIKEY_PROVIDERS … 231 entries Registry has 233; frozen gate says 231 #11450
Unit 3/4 · CC create route ×2 (cc prefix 400 ≠ 403/201) "cc" reserved by claude registry alias (open-sse/config/providers/registry/claude/index.ts:16) rejects before flag check #11450
Unit 3/4 · openapi operation floor Tip added undocumented ops faster than spec (floor rebaseline needed vs current tip tree) #11450 (re-measure needed — see note below)
Unit 3/4 · t06 volcengine Zod ×1 (+ Qwen Code JSON noise) volcengine-plan/connect* routes call request.json() unvalidated #11450
Unit 4/4 · antigravity postExchange retry ×1 {done:true} onboardUser ack skips discovery retry (antigravity.ts) #11450
No new ESLint warnings (exit 1 now, not 2 — prune worked) Tip-wide unused-vars debt, e.g. 22 in open-sse/services/combo.ts alone landed with #11493; ~275 uncovered errors tree-wide NOT covered by #11450 (it fixed only 5 sites) — needs its own cleanup pass or ratchet baseline refresh at this tip
Build (advisory) useLiveDashboard.ts:16 imports resolveLiveWsUrl/sanitizeLiveWsPort which stopped existing at tip commit d82b6827 (#11452) — broken on the base itself, advisory-only nobody yet

Why I'm not porting all of #11450 into this PR: you explicitly scoped this one to "only the electron-smoke commit", and duplicating its fixes here would guarantee conflicts when both land. Cleanest sequence: land #11450 first (it is fully green against .51 as of its latest run), then this PR rebase-drops onto it and every unit row above disappears; the two items #11450 does not cover (tip-wide ESLint debt, advisory Build import break) deserve their own small PRs against .51.

Two measurement notes for #11450 before merge: (a) the provider count moved 352 → 353 with the new tip commits, so its docs-count claims must be re-measured at a179ffed5; (b) its openapi floor re-baseline (34.4 @ d82b6827) needs re-verification at the same tip. Happy to do either here if you'd rather consolidate.

@diegosouzapw
diegosouzapw merged commit 9862f12 into diegosouzapw:release/v3.8.51 Aug 25, 2026
9 of 16 checks passed
diegosouzapw pushed a commit to jonlwheat2-gif/OmniRoute that referenced this pull request Aug 25, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…7592 cold-restart smoke (diegosouzapw#11443)

Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused test: electron-smoke-script.test.ts — part of batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Real hardware validation on a fresh packaged Windows build (Hard Rule diegosouzapw#18) surfacing and fixing 5 genuine defects (GNU-tar drive-letter parsing, missing asar file, electron-builder's node_modules extraResources drop, USERPROFILE-derived userData path, unreachable driver-log assertion) is exactly the kind of investigation this repo needs more of. Thank you.
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.

fix(startup): better-sqlite3 not unpacked from app.asar → native module load fails → sql.js fallback OOM crash

3 participants