desktop: fix packaged app crash on startup (bundle the bearer helpers, not the server) - #1018
Conversation
…fix packaged crash) The Electron main process imported `loadOrMintLocalAuthToken` / `rotateLocalAuthToken` from the `@executor-js/local` barrel, which also exports the full local server (`startServer`). That dragged the entire server module graph — including `ws` — into the main bundle. electron-vite's externalizeDepsPlugin then externalized `ws`, but `ws` is not a declared desktop dependency, so electron-builder never bundled it. The result: the PACKAGED app crashed on startup with `ERR_MODULE_NOT_FOUND: Cannot find package 'ws'`. Dev electron hid this — it resolves `ws` from the monorepo node_modules. The auth helpers in apps/local/src/auth.ts are pure `node:fs`/`node:crypto` with no server or Effect imports. Expose them via a dedicated `@executor-js/local/auth` subpath and import from it in the desktop main; the sidecar process (src/sidecar/server.ts) keeps the full barrel since it runs the server. `ws` references in the built main bundle drop from 547 to 0. Proven end-to-end: the packaged Linux bundle, launched headless under Xvfb in a VM, now boots and logs "attaching to supervised daemon …" and the manifest stays kind=cli-daemon with the daemon's pid (attached, not a spawned sidecar) — e2e/scripts/verify-linux-desktop-attach.ts.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | b1211f4 | Commit Preview URL Branch Preview URL |
Jun 14 2026, 05:08 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | b1211f4 | Jun 14 2026, 05:08 PM |
Cloudflare previewTorn down — the PR is closed. |
Greptile SummaryThis PR fixes a packaged-app crash by introducing a
Confidence Score: 4/5Safe to merge; the fix is minimal and targeted, the desktop main no longer pulls in any server-side dependencies. The three production-code changes are each a single import-path swap, and auth.ts is demonstrably dependency-free (only Node built-ins). The one rough edge is in the manual e2e harness: the daemon-manifest polling loop exits silently on timeout, which would surface as a confusing SyntaxError rather than a clear diagnostic — but this is a test-script quality issue, not a regression in the shipped code. e2e/scripts/verify-linux-desktop-attach.ts — the daemon-readiness polling loop could be more defensive on timeout. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Electron main process] -->|was: import from @executor-js/local| B[Full barrel index.ts]
B -->|re-exports| C[startServer imports ws]
C -->|electron-vite externalizes ws| D[ws missing from asar - crash]
A2[Electron main process] -->|now: import from @executor-js/local/auth| E[auth.ts subpath]
E -->|only node:fs and node:crypto| F[no ws, no Effect, no crash]
G[Sidecar binary - separate process] -->|still imports from @executor-js/local| B
B -->|ws is a dependency of sidecar binary| H[ws bundled in sidecar binary]
Reviews (1): Last reviewed commit: "desktop: import the local bearer helpers..." | Re-trigger Greptile |
|
|
||
| log("starting bundled sidecar as supervised daemon..."); | ||
| await vm.ssh( | ||
| `rm -rf ${home}; mkdir -p ${home}; nohup env HOME=${home} EXECUTOR_SUPERVISED=1 EXECUTOR_DATA_DIR=${home}/.executor EXECUTOR_PORT=4789 EXECUTOR_HOST=127.0.0.1 EXECUTOR_AUTH_TOKEN=linux-attach EXECUTOR_CLIENT_DIR=${webui} ${sidecar} > /tmp/daemon.log 2>&1 &`, | ||
| ); | ||
| // Wait for the daemon to publish its manifest + serve. | ||
| for (let i = 0; i < 30; i++) { | ||
| const r = await vm.ssh(`cat ${home}/.executor/server-control/server.json 2>/dev/null`); |
There was a problem hiding this comment.
Silent timeout before manifest parse
If the daemon never writes server.json within the 30-second poll window the loop exits silently, and the subsequent JSON.parse(before) on line 92 will throw a SyntaxError (empty string) rather than a clear diagnostic. The intent check on line 95 (if (beforeManifest.kind !== "cli-daemon") throw …) only helps if the file exists with wrong content. Adding a throw after the loop on timeout would surface the actual failure condition immediately.
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
The bug
The packaged desktop app crashed on startup:
The Electron main process imports
loadOrMintLocalAuthToken/rotateLocalAuthTokenfrom the@executor-js/localbarrel (./src/index.ts),which also exports the full local server (
startServer). That pulls the entireserver module graph — including
ws— into the main bundle.electron-vite'sexternalizeDepsPluginexternalizesws, butwsisn't a declared desktopdependency, so
electron-buildernever bundles it into the asar. Dev electronhides this because it resolves
wsfrom the monoreponode_modules; only thepackaged artifact hits it.
The fix
The token helpers in
apps/local/src/auth.tsare purenode:fs/node:crypto— no server, no
ws, no Effect. Expose them via a dedicated@executor-js/local/authsubpath and import from it in the desktop main.The sidecar process (
src/sidecar/server.ts) keeps the full barrel because itactually runs the server.
wsreferences in the built main bundle drop 547 → 0.Proof
The packaged Linux bundle, launched headless under Xvfb in a VM, now boots
and logs
attaching to supervised daemon at http://127.0.0.1:4789 (pid …), andthe server manifest stays
kind: cli-daemonwith the daemon's pid — i.e. thepackaged app attached to the supervised daemon rather than spawning its own
sidecar. The harness is
e2e/scripts/verify-linux-desktop-attach.ts(manual;Xvfb sidesteps the macOS Aqua-session limitation that gates the
desktop-packagedvitest project).