Skip to content

windows-v0.4.1: installed-CLI EPERM + agent-shell pipe-name fixes - #9

Merged
boostedchaos merged 7 commits into
mainfrom
windows-0.4.1
Aug 10, 2026
Merged

windows-v0.4.1: installed-CLI EPERM + agent-shell pipe-name fixes#9
boostedchaos merged 7 commits into
mainfrom
windows-0.4.1

Conversation

@boostedchaos

Copy link
Copy Markdown
Owner

Two field bugs found running the packaged 0.4.0 on Windows. Both made ade unusable, and both were invisible to CI, which ran the CLI straight out of a writable checkout with USERNAME set — the two conditions a real install does not have.

Bug A — installed ade died with EPERM. The launcher baked C:\Program Files\ADE\resources\cli\index.mjs, and bun refuses to EXECUTE an entry script from a directory the user cannot write to (error: EPERM reading …, though the file reads fine). Bin injection now copies the packaged bundle to <home>/<adeDir>/cli/index.mjs — unconditionally, so an upgrade refreshes it — and bakes that path. Dev checkouts and POSIX are untouched; ADE_CLI_ENTRY still overrides.

Bug B — ade in ADE's own agent panes reported "app is not running" (exit 3). Agent shells carry neither USERNAME nor USER, and under bun os.userInfo().username returns the literal "unknown" without throwing — so the existing env fallbacks never fired and the CLI dialled \\.\pipe\ade-control-unknown while the app listened on the pipe named for the real user. Fixed on both sides:

  • CLI getUserName(): rejects "unknown" at the userInfo step, then $USERNAME$USERwhoami (DOMAIN\useruser, spawned only when the cheap steps come up empty, cached) → basename($USERPROFILE)"user", sanitised with the same rule the app uses.
  • App: buildTerminalEnv injects its own os.userInfo().username as USERNAME when the environment has none (never clobbering), and USERNAME is allowlisted so it survives the terminal-host's buildSafeEnv re-filter. Fixes every user-name consumer in an agent shell, not just ade.

CI regression gate. The named-pipe smoke now invokes the generated <profile>\.ade\bin\ade.cmd (exercising the staged copy), asserts the staged bundle exists, and strips USERNAME/USER first (exercising the whoami path). It fails against 0.4.0 behavior.

Evidence

  • Canary — the new tests fail against 0.4.0 code: env.test.ts USERNAME block 3 fail (Expected "unknown", Received undefined — bun's sentinel, observed live); socket-path.test.ts and ade-cli-bin.test.ts fail to import getUserName / stageBundledCliEntry.
  • Live against the RUNNING installed 0.4.0, with USERNAME and USER removed from the environment: patched CLI → list-workspaces exit 0 and the real workspace table; 0.4.0 CLI, same command and env → ADE app is not running (no control socket), exit 3.
  • Suites from each package cwd: server-core 482 pass / 20 skip / 0 fail (was 475/20/0), cli 205 pass / 91 skip / 0 fail. Pinned tsc exit 0 for both.

Draft: release prep (0.4.1 bump, CHANGELOG, WINDOWS.md, build-report addendum) is included; publishing is gated on the orchestrator.

…t exec from Program Files

The installed 0.4.0 launcher baked
C:\Program Files\ADE\resources\cli\index.mjs and every `ade` call died with
"error: EPERM reading <path>". bun refuses to EXECUTE an entry script from a
directory the user cannot write to; the file itself reads fine, and the same
bytes run from a temp dir.

Bin injection now copies a PACKAGED entry to <home>/<adeDir>/cli/index.mjs —
unconditionally, so an upgrade refreshes the copy — and bakes that path into
the launcher. A dev checkout's TypeScript entry is left alone: it imports
siblings and its tree is writable anyway. ADE_CLI_ENTRY and the exit-127
missing-entry guard are unchanged.
…agent shells

ADE's own agent terminals carry neither USERNAME nor USER, and under bun
os.userInfo().username then returns the literal string "unknown" instead of
throwing — so the existing try/catch env fallbacks never fired and every `ade`
call in an agent pane dialled \.\pipe\ade-control-unknown while the app
listened on the pipe named for the real user (exit 3, "app is not running").

getUserName() walks lazy steps: userInfo (rejecting "unknown" only there, so a
real account of that name is still served by the later steps), $USERNAME,
$USER, whoami (DOMAIN\user -> user, spawned only when the cheap steps came up
empty, cached per process), basename($USERPROFILE), then the "user" literal.
Sanitisation is the same [^A-Za-z0-9-] rule the app applies, so both sides
always name the same pipe.
Windows never sets USER and USERNAME was not on the terminal allowlist, so ADE
agent shells had no user name at all — which is what made bun report "unknown"
and sent the `ade` CLI to a pipe nobody listens on. buildTerminalEnv now
injects the app's own os.userInfo().username when the environment has none
(never clobbering one that came through), and USERNAME is allowlisted so it
survives the terminal-host's buildSafeEnv re-filter. Fixes every user-name
consumer in an agent shell, not just `ade`.
The old smoke ran the CLI straight out of a writable checkout with USERNAME
set — the two conditions a real install does not have — which is why it stayed
green while `ade` was unusable both from the Start-menu install and from ADE's
own agent panes.

The smoke now invokes the generated <profile>\.ade\bin\ade.cmd (exercising the
staged CLI copy, since bun cannot execute the entry from the read-only install
dir), asserts the staged bundle exists, and strips USERNAME/USER first
(exercising the CLI's whoami fallback). Marker-file judgment and the one retry
are unchanged.
Both fixes are Windows-only in effect; the version is the desktop app's, which
is what electron-builder names the installer from. Build-report addendum
records root causes, canary evidence and the live verification against the
running installed 0.4.0; release URL and CI run IDs are left as TODO slots.
Ship-verifier observations, all cheap and all in code this release already
touches.

stageBundledCliEntry now copies to a sibling temp name and renames over
index.mjs, so a crash (or an `ade` call reading the file) mid-copy can no
longer leave a truncated entry bun refuses to run. When staging fails and a
previously staged copy exists, the launcher keeps pointing at THAT copy — the
read-only packaged path is the 0.4.0 bug, so a stale-but-executable entry
beats a fresh unusable one. Only a failure with nothing staged falls back to
the packaged path. Tests cover both fallback branches.

The "unknown" sentinel is now rejected at every step that can carry it, not
just at userInfo(): any bun-hosted parent that resolved "unknown" itself and
exported it as USERNAME/USER would both re-create the bug and suppress the
whoami fallback. Same guard on the app side — buildTerminalEnv skips the
injection rather than propagating the sentinel into agent shells, and takes a
userInfoUser seam so the test pins a real name instead of comparing against
os.userInfo() (self-referential under bun-on-Windows: both sides read
"unknown" and the assertion passed on the bug).
The report said POSIX "was checked and deliberately left alone", implying the
staging is Windows-only. It is not: stageBundledCliEntry has no platform guard
and electron-builder ships resources/cli/index.mjs on mac too, so mac
launchers also bake the staged data-dir copy. Deliberate — bun-on-posix has no
execute restriction, so staging there is harmless — but the doc claimed
something the code does not do. No platform guard added; uniform behaviour is
the simpler contract.

Also records verifier observation #5 as an accepted known gap: the app-side
USERNAME injection has no end-to-end CI gate, and its unit coverage sits in
the server-core suite (ADE CI) rather than Windows CI.
@boostedchaos
boostedchaos marked this pull request as ready for review August 10, 2026 00:28
@boostedchaos
boostedchaos merged commit 41a455c into main Aug 10, 2026
3 checks passed
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.

1 participant