From 655ff0132da02925e5dc015f1e440adeabcc006a Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 13 Jul 2026 12:14:29 -0700 Subject: [PATCH 1/2] fix(code): run Electron postinstall via Node on Windows Replace bash postinstall with a Node script so pnpm install no longer hits the WSL bash stub, and document the Windows setup gap for phrocs. Co-authored-by: Cursor --- README.md | 6 +++ apps/code/package.json | 2 +- apps/code/scripts/postinstall.mjs | 87 +++++++++++++++++++++++++++++++ apps/code/scripts/postinstall.sh | 46 ---------------- docs/TROUBLESHOOTING.md | 14 ++++- 5 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 apps/code/scripts/postinstall.mjs delete mode 100755 apps/code/scripts/postinstall.sh diff --git a/README.md b/README.md index 5675188121..2dd304c1d7 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ cp .env.example .env ``` +### Windows notes + +- Prerequisites: Node.js 22+, pnpm 10.23+. Git for Windows is useful for contributing; you do **not** need WSL or Git Bash for `pnpm install`. +- `pnpm install` runs `apps/code/scripts/postinstall.mjs` via Node (not bash), so it does not hit the System32 WSL `bash.exe` stub. +- `pnpm dev` still relies on phrocs, which has no Windows binary yet. Until that is fixed, use `pnpm dev:agent` and `pnpm dev:code` in two terminals, or `pnpm dev:mprocs`. + ### Running in Development By default, `pnpm dev` uses phrocs (our custom process runner) to run the agent and code app in parallel. phrocs auto-installs on first run and reads the `mprocs.yaml` config file. The binary is downloaded to `bin/phrocs` and is git-ignored. diff --git a/apps/code/package.json b/apps/code/package.json index 543b63f79b..a2a7ccf49f 100644 --- a/apps/code/package.json +++ b/apps/code/package.json @@ -27,7 +27,7 @@ "test": "vitest run", "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts", "test:e2e:headed": "playwright test --config=tests/e2e/playwright.config.ts --headed", - "postinstall": "bash scripts/postinstall.sh", + "postinstall": "node scripts/postinstall.mjs", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "clean": "node ../../scripts/rimraf.mjs .vite .turbo out node_modules/.vite" diff --git a/apps/code/scripts/postinstall.mjs b/apps/code/scripts/postinstall.mjs new file mode 100644 index 0000000000..e409e51362 --- /dev/null +++ b/apps/code/scripts/postinstall.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node +// Postinstall for the Electron app: rebuild native modules for Electron's ABI, +// restore spawn-helper execute bits, patch the macOS Electron display name, and +// download bundled binaries. Pure Node so Windows does not need bash/Git Bash +// (pnpm otherwise hits the System32 WSL bash stub when no distro is installed). + +import { execFileSync } from "node:child_process"; +import { chmodSync, existsSync, readdirSync, statSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +// apps/code/scripts -> repo root (pnpm package cwd is apps/code, so the old +// bash script used `cd ../..` from cwd; from this file we need one more `..`). +const REPO_ROOT = join(__dirname, "..", "..", ".."); +const SCRIPTS_DIR = __dirname; + +function electronDistMissing(electronDist) { + if (!existsSync(electronDist)) return true; + try { + return readdirSync(electronDist).length === 0; + } catch { + return true; + } +} + +// Self-heal missing Electron binary. +// pnpm skips package-level postinstall scripts when the lockfile is already +// satisfied, so if node_modules/electron/dist gets wiped (interrupted download, +// cache eviction, arch change, manual cleanup), `pnpm install` won't notice — +// and `electron-vite dev` then fails with "Electron failed to install +// correctly, please delete node_modules/electron and try installing again". +const electronDist = join(REPO_ROOT, "node_modules", "electron", "dist"); +if (electronDistMissing(electronDist)) { + console.log(`Electron binary missing at ${electronDist} — downloading...`); + execFileSync( + process.execPath, + [join(REPO_ROOT, "node_modules", "electron", "install.js")], + { + cwd: REPO_ROOT, + stdio: "inherit", + }, + ); +} + +console.log("Rebuilding native modules for Electron..."); +execFileSync( + process.execPath, + [join(REPO_ROOT, "scripts", "rebuild-better-sqlite3-electron.mjs")], + { cwd: REPO_ROOT, stdio: "inherit" }, +); + +// Restore the execute bit on node-pty's spawn-helper. pnpm extracts node-pty's +// prebuilt binaries without preserving the executable mode, so the helper lands +// without +x and posix_spawnp fails at runtime with "posix_spawnp failed" the +// first time a terminal session is opened. Skip on Windows (no POSIX +x). +if (process.platform !== "win32") { + const prebuilds = join(REPO_ROOT, "node_modules", "node-pty", "prebuilds"); + if (existsSync(prebuilds)) { + for (const entry of readdirSync(prebuilds)) { + const helper = join(prebuilds, entry, "spawn-helper"); + if (!existsSync(helper)) continue; + const mode = statSync(helper).mode; + if ((mode & 0o111) === 0) { + console.log(`Restoring execute bit on ${helper}`); + chmodSync(helper, mode | 0o111); + } + } + } +} + +// Info.plist lives inside Electron.app — macOS only. Reuse the existing script. +if (process.platform === "darwin") { + console.log("Patching Electron app name..."); + execFileSync("bash", [join(SCRIPTS_DIR, "patch-electron-name.sh")], { + cwd: REPO_ROOT, + stdio: "inherit", + }); +} + +console.log("Downloading binaries..."); +execFileSync(process.execPath, [join(SCRIPTS_DIR, "download-binaries.mjs")], { + cwd: REPO_ROOT, + stdio: "inherit", +}); + +console.log("Postinstall complete."); diff --git a/apps/code/scripts/postinstall.sh b/apps/code/scripts/postinstall.sh deleted file mode 100755 index 4f12df1a62..0000000000 --- a/apps/code/scripts/postinstall.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -# Postinstall script for the Electron app -# Rebuilds native modules against Electron's Node headers and applies patches - -set -e - -REPO_ROOT="$(cd ../.. && pwd)" -SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)" - -# Self-heal missing Electron binary. -# pnpm skips package-level postinstall scripts when the lockfile is already -# satisfied, so if node_modules/electron/dist gets wiped (interrupted download, -# cache eviction, arch change, manual cleanup), `pnpm install` won't notice — -# and `electron-vite dev` then fails with "Electron failed to install -# correctly, please delete node_modules/electron and try installing again". -# Detect the missing binary and invoke Electron's own install script to fetch it. -ELECTRON_DIST="$REPO_ROOT/node_modules/electron/dist" -if [ ! -d "$ELECTRON_DIST" ] || [ -z "$(ls -A "$ELECTRON_DIST" 2>/dev/null)" ]; then - echo "Electron binary missing at $ELECTRON_DIST — downloading..." - node "$REPO_ROOT/node_modules/electron/install.js" -fi - -echo "Rebuilding native modules for Electron..." - -cd "$REPO_ROOT" -node scripts/rebuild-better-sqlite3-electron.mjs - -# Restore the execute bit on node-pty's spawn-helper. pnpm extracts node-pty's -# prebuilt binaries without preserving the executable mode, so the helper lands -# without +x and posix_spawnp fails at runtime with "posix_spawnp failed" the -# first time a terminal session is opened. Re-mark every prebuilt helper executable. -for helper in "$REPO_ROOT"/node_modules/node-pty/prebuilds/*/spawn-helper; do - if [ -f "$helper" ] && [ ! -x "$helper" ]; then - echo "Restoring execute bit on $helper" - chmod +x "$helper" - fi -done - -echo "Patching Electron app name..." -bash "$SCRIPTS_DIR/patch-electron-name.sh" - -echo "Downloading binaries..." -node "$SCRIPTS_DIR/download-binaries.mjs" - -echo "Postinstall complete." diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index d3fc3d4628..1eac1c4b8c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -1,5 +1,17 @@ # Troubleshooting +## Windows: `pnpm install` fails with WSL bash / `execvpe(/bin/bash)` + +If install dies in `apps/code` postinstall with: + +``` +<3>WSL (9 - Relay) ERROR: execvpe(/bin/bash) failed: No such file or directory +``` + +an older checkout is still running `bash scripts/postinstall.sh`, and Windows is resolving `bash` to the System32 WSL stub without a distro. Current `main` uses `node scripts/postinstall.mjs` — pull latest and re-run `pnpm install`. WSL/Git Bash are not required for install. + +`pnpm dev` / phrocs on Windows is a separate issue: use `pnpm dev:agent` + `pnpm dev:code`, or `pnpm dev:mprocs`, until a Windows runner path lands. + ## Black screen during development If the app launches but renders a blank/black screen, it's almost always a stale Vite cache. @@ -45,7 +57,7 @@ If the app crashes with something like: libc++abi: terminating due to uncaught exception of type Napi::Error ``` -A native module was built for the wrong runtime. Re-run the install, which rebuilds what Electron needs via `apps/code/scripts/postinstall.sh`: +A native module was built for the wrong runtime. Re-run the install, which rebuilds what Electron needs via `apps/code/scripts/postinstall.mjs`: ```bash pnpm install From 174d1c01cbaed7fd24406474b43a2750a754330d Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 13 Jul 2026 12:22:02 -0700 Subject: [PATCH 2/2] docs: tighten Windows postinstall guidance before PR Prefer the two-terminal dev workaround wording and shorten the Electron dist-repair comment. Co-authored-by: Cursor --- README.md | 2 +- apps/code/scripts/postinstall.mjs | 8 ++------ docs/TROUBLESHOOTING.md | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2dd304c1d7..eeb1a42777 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ cp .env.example .env - Prerequisites: Node.js 22+, pnpm 10.23+. Git for Windows is useful for contributing; you do **not** need WSL or Git Bash for `pnpm install`. - `pnpm install` runs `apps/code/scripts/postinstall.mjs` via Node (not bash), so it does not hit the System32 WSL `bash.exe` stub. -- `pnpm dev` still relies on phrocs, which has no Windows binary yet. Until that is fixed, use `pnpm dev:agent` and `pnpm dev:code` in two terminals, or `pnpm dev:mprocs`. +- On Windows, run `pnpm dev:agent` and `pnpm dev:code` in separate terminals. `pnpm dev` still depends on phrocs (no Windows binary yet). `pnpm dev:mprocs` is also available as an alternative. ### Running in Development diff --git a/apps/code/scripts/postinstall.mjs b/apps/code/scripts/postinstall.mjs index e409e51362..9fcee8b05c 100644 --- a/apps/code/scripts/postinstall.mjs +++ b/apps/code/scripts/postinstall.mjs @@ -24,12 +24,8 @@ function electronDistMissing(electronDist) { } } -// Self-heal missing Electron binary. -// pnpm skips package-level postinstall scripts when the lockfile is already -// satisfied, so if node_modules/electron/dist gets wiped (interrupted download, -// cache eviction, arch change, manual cleanup), `pnpm install` won't notice — -// and `electron-vite dev` then fails with "Electron failed to install -// correctly, please delete node_modules/electron and try installing again". +// pnpm may not rerun Electron's package-level postinstall when the lockfile is +// unchanged. Repair a missing or empty dist directory here. const electronDist = join(REPO_ROOT, "node_modules", "electron", "dist"); if (electronDistMissing(electronDist)) { console.log(`Electron binary missing at ${electronDist} — downloading...`); diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 1eac1c4b8c..27fc45702b 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -10,7 +10,7 @@ If install dies in `apps/code` postinstall with: an older checkout is still running `bash scripts/postinstall.sh`, and Windows is resolving `bash` to the System32 WSL stub without a distro. Current `main` uses `node scripts/postinstall.mjs` — pull latest and re-run `pnpm install`. WSL/Git Bash are not required for install. -`pnpm dev` / phrocs on Windows is a separate issue: use `pnpm dev:agent` + `pnpm dev:code`, or `pnpm dev:mprocs`, until a Windows runner path lands. +`pnpm dev` / phrocs on Windows is a separate issue. Prefer `pnpm dev:agent` and `pnpm dev:code` in separate terminals; `pnpm dev:mprocs` is also available as an alternative. ## Black screen during development