Skip to content

Make dor / dor ab work on Windows#188

Merged
nedtwigg merged 11 commits into
mainfrom
ab-install
Jun 30, 2026
Merged

Make dor / dor ab work on Windows#188
nedtwigg merged 11 commits into
mainfrom
ab-install

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

Gets the dor CLI and dor ab (agent-browser surfaces) working on Windows. Each layer of breakage was only reachable once the previous one was fixed, so this is a stack of related fixes.

What was broken, in order

  1. Spawning external binaries — Node's spawn ignores PATHEXT (ENOENT on a bare agent-browser) and Node ≥22 refuses to spawn a .cmd even by absolute path (EINVAL). Route all external spawns through cross-spawn. (8270828)
  2. dor launcher — the \?\ verbatim path Tauri's resource_dir() returns flowed into DORMOUSE_CLI_BIN/DORMOUSE_CLI_JS; cmd.exe can't execute a batch file via a verbatim path ("The system cannot find the path specified."). Normalize once at the boundary (resolve_sidecar_path). (1c95bb2, 3188b92)
  3. dor ab hangagent-browser open leaves a daemon that on Windows inherits our stdout/stderr pipes, so 'close' never fires. Resolve on 'exit' with a short grace. (3d7b9cd)
  4. Focus-stealing console flashes — cross-spawn runs .cmd through cmd.exe; the screenshot loop spawned one per frame. windowsHide: true. (9d30c27)
  5. Burst of blank lines — the lingering daemon scribbles into the inherited pipes during the grace window. Snapshot captured output at 'exit'. (aa252a6)

Dev ergonomics

  • scripts/free-dev-port.mjs now also reaps an orphaned sidecar node.exe locking target\debug before tauri dev (its JobObject leash can leak on a force-quit/crash). (4c875ea)

Notes

  • New spec section: docs/specs/dor-cli.md → "Spawning External Binaries".
  • The cross-package duplication between the two spawn helpers is tracked as a follow-up (dor-lib-common).

🤖 Generated with Claude Code

nedtwigg and others added 10 commits June 20, 2026 10:07
`dor ab` failed with "agent-browser was not found" on Windows even when
agent-browser ran fine in the same shell. Two Windows-only spawn failures
were behind it, both reproduced:

- ENOENT: Node's spawn ignores PATHEXT, so a bare `agent-browser` never
  resolves the `.cmd` PATH shim that npm/vfox installs.
- EINVAL: Node >=22 refuses to spawn a `.cmd`/`.bat` even by absolute path
  (CVE-2024-27980 hardening).

Route every external/user-binary spawn through cross-spawn, which resolves
via PATH/PATHEXT, runs `.cmd` through cmd.exe with correct escaping, and is a
no-op on POSIX:
- dor CLI (execAgentBrowserProcess)
- agent-browser host (spawnAgentBrowser) — hits this even for the absolute
  binaryPath dor ab resolves, since GUI hosts don't share the shell PATH
- standalone dev harness (pnpm + agent-browser)

dor.js is ESM and cross-spawn is CommonJS, so add an esbuild createRequire
banner to its build (the per-host CJS bundles need no shim). Resolve the
binary path once per invocation and reuse it for both the missing-install
pre-check and the host request, dropping a redundant PATH walk.

Also set ELECTRON_RUN_AS_NODE in the bin launchers: when DORMOUSE_NODE is the
editor's Electron binary, without it Electron launches its GUI, ignores the
script, and exits 0 — so `dor` would silently do nothing.

Document the rule in docs/specs/dor-cli.md ("Spawning External Binaries")
with a pointer from dor-browser.md. Make two POSIX-only tests platform-aware
(create a `.cmd` shim and join PATH with the platform delimiter on Windows).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… paths

Tauri's resource_dir() returns \?\ verbatim paths in the bundled/dev
layout. Those flowed into DORMOUSE_CLI_BIN (prepended to PATH) and
DORMOUSE_CLI_JS unchanged, so cmd.exe could not execute dor.cmd via the
verbatim path and `dor` failed with "The system cannot find the path
specified." Strip the prefix in dor_cli_paths_from_root, mirroring
sidecar_script_arg_path, and add a Windows regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… close

agent-browser's `open` launches a detached per-session daemon that on
Windows inherits the parent's stdout/stderr pipes. Those pipes never reach
EOF while the daemon lives, so the child's 'close' event never fires and
the spawn helpers — which awaited 'close' — hung forever. POSIX dodges this
because the daemon double-forks and detaches from the inherited fds, which
is why it only ever reproduced on Windows.

Both spawn helpers (dor/src/commands/agent-browser.ts and the shared
agent-browser host) now resolve on 'exit', which fires when the foreground
process ends regardless of lingering pipe holders, giving 'close' a short
grace (CLOSE_GRACE_MS) to win first so a normal command's full output still
flushes. Add 'exit' to dor's ambient ChildProcess type and document the
rule in docs/specs/dor-cli.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-spawn deps were added to dor/lib/standalone package.json but the
lockfile was never regenerated alongside them. Add the cross-spawn entry
(and its which/isexe/path-key/shebang transitive deps) so the lockfile
matches the manifests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Windows an interrupted `tauri dev` leaves two strays that break the next
run: an orphaned vite squatting on the dev port ("Port 1420 is already in
use") and an orphaned sidecar node.exe running out of target\debug that
locks the binary, so the next Rust rebuild fails copying it with "Access is
denied." Add scripts/free-dev-port.mjs to kill both (Windows-only no-op
otherwise) and run it from the root `dev:standalone` script before Tauri.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spell the intra-package script calls as `pnpm run stage` rather than the
`pnpm stage` shorthand, so they always resolve as scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cross-spawn runs the agent-browser `.cmd` shim through cmd.exe. Without
windowsHide, Node shows a console window for each spawn — and the panel's
screenshot loop spawns one per stream-frame pulse on a live page, so a
console window flashed and stole focus several times a second, making the
app unusable. Pass windowsHide: true at both spawn sites (no-op off Windows)
and add it to dor's ambient spawn option types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`dor ab open` printed a burst of blank lines on Windows. The forwarded
agent-browser process produces clean output over a pipe (it detects the
non-TTY and skips its animation), but `open` leaves a daemon alive that on
Windows inherits our stdout/stderr pipes and keeps scribbling into them.
Because we resolve the spawn on 'exit' + a short grace (the daemon keeps
'close' from ever firing), that post-command daemon noise was captured
during the grace window and replayed to the terminal. macOS never saw it
because its daemon detaches from the inherited fds.

Snapshot the captured output at 'exit' — when the foreground command is
done — and resolve the grace path with that snapshot, so only the command's
own output is returned. The 'close' path still uses the live buffers (no
lingering daemon, so nothing extra to drop). Applied to both spawn helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strip the `\?\` verbatim prefix once in resolve_sidecar_path (the entry
boundary) instead of re-stripping it at each consumer. Deletes the
sidecar_script_arg_path helper and the sidecar_arg_path indirection in
start_sidecar, and drops the strip from dor_cli_paths_from_root — every
derived path is now plain for free. Retarget the regression test to the
boundary and update the dor-cli spec reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9cb1afd
Status: ✅  Deploy successful!
Preview URL: https://26ddd150.mouseterm.pages.dev
Branch Preview URL: https://ab-install.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows fixes themselves read well — routing spawns through cross-spawn, normalizing the \\?\ verbatim path once at the resolve_sidecar_path boundary, the exit/close grace for the lingering daemon, and setting ELECTRON_RUN_AS_NODE in the launchers are all clearly reasoned.

One blocking issue: Build & Test is red because dor/test/snapshots/ensure-json.snap no longer matches. The ensure json output test runs dor ensure --cwd /Users/me/projects/site, and callerWorkingDirectory resolves that absolute --cwd via resolvePath, which returns it verbatim on Linux. The committed snapshot was regenerated on Windows (C:\\Users\\me\\projects\\site), so on the Linux CI runner actual=/Users/me/projects/site ≠ expected. Inline fix below restores it.

Root cause worth a note: this snapshot is platform-dependent — on win32 resolvePath('/Users/me/projects/site') folds to the current drive, so a future Windows CI leg would re-break it the other direction. If Windows CI is on the roadmap, consider feeding a --cwd that resolves identically on both platforms (or normalizing the path in the snapshot harness) so the fixture stays deterministic.

Comment thread dor/test/snapshots/ensure-json.snap Outdated
…m committed

The `ensure json output` snapshot resolves `--cwd /Users/me/projects/site`
through node:path, which on win32 folds it to the current drive
(`C:\Users\me\projects\site`) and JSON-escapes the separators. Committing the
Windows form turned Linux CI red (and vice versa).

Keep the snapshot in its Unix form and smudge the produced output back to that
form as it's captured — only on win32, only for a `X:\...` token so it can't
touch escapes like `\n`. A snapshot regenerated on Windows now writes the Unix
form, and the comparison stays byte-exact. Apply the same smudge to the one
argv deepEqual test that resolved a POSIX PWD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nedtwigg nedtwigg merged commit 9ef3aae into main Jun 30, 2026
9 checks passed
@nedtwigg nedtwigg deleted the ab-install branch June 30, 2026 03:49
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