Skip to content

fix(coding-agent): Windows platform seams: kernel python path, tilde joins, hidden spawns, handshake budgets, bash and Herdr socket resolution - #2036

Open
snimu wants to merge 14 commits into
mainfrom
fix/windows-platform-seams
Open

fix(coding-agent): Windows platform seams: kernel python path, tilde joins, hidden spawns, handshake budgets, bash and Herdr socket resolution#2036
snimu wants to merge 14 commits into
mainfrom
fix/windows-platform-seams

Conversation

@snimu

@snimu snimu commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Prime Agent misbehaves on native Windows in six independent places. Each fix below is behind a platform check (or a platform-neutral mechanism), covered by a unit test that exercises the win32 branch via an injected platform, and verified to fail on the pre-fix code.

Fixes

  1. Kernel Python bootstrap always failed on Windows. Both bootstrap sites joined <venv>/bin/python, but uv creates <venv>\Scripts\python.exe on win32 (the same file already branches for uv.exe). One kernelVenvPython helper now owns the layout. Fixes the defect reported in discussions [Bug] Kernel bootstrap fails on native Windows — venv Python resolved as bin/python #1401 and Bug report: Prime Agent on Windows - three reproducible breakages (with ready fixes) #1969 (part 1).
  2. ~/ expansion produced mixed-separator paths (C:\Users\u/rest) at three sites: expandPath, expandTildePath, and an inline copy for PI_PACKAGE_DIR. Expansion now joins through the platform path module, and the inline copy delegates to expandTildePath. Fixes the defect reported in discussions [Bug] expandTildePath produces mixed path separators on Windows #1442 and [Bug] expandPath in path-utils.ts produces mixed path separators on Windows (same pattern as #1442) #1469.
  3. Console windows flashed on every background spawn. Node's windowsHide defaults to false and exactly one call site in the repo set it, so console-subsystem children of the windowless daemon and detached workers allocated fresh consoles. utils/child-process.ts now exports spawnHidden / spawnSyncHidden / execSyncHidden / execFileHidden / execFileSyncHidden, and all 49 non-interactive coding-agent spawn/exec sites route through them (the tui package's two background spawns set the flag inline). The six interactive spawns (editors, inherit-stdio installers/updates/relaunches) intentionally keep their console. No-op off Windows. Fixes the defect reported in discussion [Bug] Background subprocesses flash console windows on native Windows #1461.
  4. Worker handshakes could never succeed on slow machines. connectWorker gave every hello/worker_auth attempt a fixed 1s clock inside its retry loop, so a handshake that consistently needs longer (the win32 process-identity probe alone is a 0.2–3s synchronous powershell spawn under AV scanning) failed every retry regardless of the 30s outer budget — and the adopt/recover paths shrank even the outer budget to 1.5–2s. Per-attempt waits now consume the remaining outer deadline (handshakeBudgetMs), every connectWorker caller uses the single 30s constant, and the downward 1s hello/auth overrides in daemon-mode and the routed client are deleted in favor of the clients' 3s defaults. Only failure paths get slower. Fixes the defect reported in discussions [Bug] Windows named-pipe daemon: session worker listens but create times out on hello/worker_auth #1622 and [Bug] Windows: session create always fails — worker handshake budgets cannot accommodate AV-slowed process launches #1678.
  5. findBashOnPath picked WSL's System32\bash.exe (a Linux-side launcher, not a Windows shell) because it took the first where bash.exe match. Matches under %SystemRoot% are now only a last resort, so per-user Git Bash installs (scoop/winget) win. Fixes the defect reported in discussion [Bug] Windows shell resolution picks WSL bash.exe over Git Bash, breaking Windows commands #1437.
  6. The built-in Herdr reporter never connected on Windows. It dialed Herdr's unix-style socket path verbatim and swallows connect errors by design, so the failure was invisible. Unmapped paths are now translated into the \\.\pipe\ namespace before dialing. Fixes the defect reported in discussion [Bug] Built-in Herdr reporter never connects on Windows, leaving panes stuck on Unknown #1399.

An earlier revision of this branch also added a 5s per-pid cache for the win32 process start id. It was reverted here: the start id exists to detect pid recycling, and a cached id could hand a recycled pid the dead process's identity — its consumers decide taskkill /T and shutdown-wait completion from that identity. Review flagged it (thread on session-lease.ts:127) and no caller is both hot and staleness-tolerant; the handshake latency problem is already covered by fix 4.

Size and shape

Total src: +221/−121 (net +100); tests: +95/−7 (net +88).
New mechanism is ~95 lines: the five hidden-spawn wrappers plus four small helpers (kernelVenvPython, handshakeBudgetMs, orderWindowsBashCandidates, herdrSocketTarget). Deletions remove the duplicated tilde-expansion copy, the fixed per-attempt handshake clocks and downward timeout overrides, and the one inline windowsHide; the rest of the churn is 1:1 call-site migration to the wrappers.

  • tests: +109 / −7. One regression pin per fix, merged into existing test files; each verified to fail on pre-fix code. Two existing assertions updated for the new spawn call shape.

Validation

  • npm run check (biome, tsgo, installer, browser-smoke) green.
  • Full coding-agent test:ci (4648 passed), test:process, test:kernel, and the tui suite (750 passed) green in a clean Linux container; the only failures are the environment's known root/lsof-dependent set, identical before and after this change.

CodeQL note

The two "shell command built from environment values" alerts on utils/child-process.ts are pre-existing flows relocated by the call-site migration: the same rule already flags main at core/kernel/bootstrap.ts:374/:392 (alerts #1 and #5), and those exact sinks now live inside the shared wrappers. The wrappers take command + argument arrays; the shell-string form (execSyncHidden) exists only for the pre-existing intentional shell callers (user-configured config-resolution commands, fixed-string clipboard fallbacks). No new shell surface is introduced by this PR.

Linear: https://linear.app/primeintellect/issue/RES-1268/discussion-sweep-p5-windows-seam-pack-spawnhidden-venv-path-homedir

Note

Fix Windows platform seams across hidden spawns, kernel paths, tilde joins, handshake budgets, bash and Herdr sockets

  • Adds spawnHidden, spawnSyncHidden, execSyncHidden, execFileHidden, and execFileSyncHidden helpers in child-process.ts that force windowsHide: true; migrates ~20 call sites across daemon, CLI, kernel, git, shell, clipboard, package-manager, and TUI modules to use them instead of direct node:child_process APIs
  • Adds kernelVenvPython in bootstrap.ts to resolve venv/Scripts/python.exe on win32 and venv/bin/python elsewhere; updates venv provisioning and kernel readiness checks to use it
  • Updates expandPath (path-utils.ts) and expandTildePath (config.ts) to join home-relative paths with platform-specific path.posix/path.win32 instead of string concatenation
  • Adds handshakeBudgetMs in daemon-supervisor.ts that computes remaining outer-deadline time (clamped to 50ms minimum); replaces fixed 1-second hello/auth timeouts with budget-derived values, and raises worker connect allowances from 1.5–2s to WORKER_CONNECT_TIMEOUT_MS (30s)
  • Adds orderWindowsBashCandidates in shell.ts to deprioritize SystemRoot bash paths, and herdrSocketTarget in herdr-agent-state.ts to map non-namespaced socket paths into the Windows named-pipe namespace
  • Behavioral Change: worker connection, recovery, and identity-restoration timeouts increase from 1.5–2s to 30s (WORKER_CONNECT_TIMEOUT_MS in daemon-supervisor.ts); supervisor hello waits in daemon-mode.ts and daemon-routed-client.ts now use default/configured timeouts instead of forced 1-second values

Macroscope summarized 8864109.


Note

Medium Risk
Wide migration of subprocess creation and longer daemon handshake waits on failure paths can affect timing and behavior on all platforms, not only Windows.

Overview
Improves native Windows behavior and centralizes a few cross-cutting fixes.

Child processes: Adds spawnHidden / spawnSyncHidden / exec*Hidden wrappers that always set windowsHide: true, and routes background daemon, kernel, git, clipboard, and tool spawns through them (TUI autocomplete and link-open spawns set the flag inline). Interactive consoles are unchanged.

Paths: kernelVenvPython picks Scripts\python.exe on win32; expandTildePath / expandPath join ~/… with the platform path module instead of string concat (fixes mixed C:\…/… paths).

Daemon: handshakeBudgetMs makes hello/auth waits use the remaining 30s connect budget; worker recovery paths use the full timeout instead of 1–2s caps; hardcoded 1s hello waits in daemon-mode/routed client are removed.

Shell / integrations: orderWindowsBashCandidates deprioritizes WSL’s System32 bash.exe; herdrSocketTarget maps unix-style Herdr socket paths into \\.\pipe\ before connecting.

Reviewed by Cursor Bugbot for commit 8864109. Bugbot is set up for automated code reviews on this repo. Configure here.

uv creates <venv>/Scripts/python.exe on Windows, but both bootstrap sites
joined <venv>/bin/python unconditionally, so first-run kernel bootstrap
always failed on native Windows. Mirror the existing win32 uv.exe branch
with one kernelVenvPython helper used at both sites.
Tilde expansion concatenated homedir() with the posix remainder at three
sites (expandPath, expandTildePath, and an inline PI_PACKAGE_DIR copy),
producing mixed-separator paths like C:\Users\u/rest on Windows. Join
through the platform path module instead and route PI_PACKAGE_DIR through
expandTildePath.
…n PATH

findBashOnPath took the first where-match, and System32\bash.exe (the WSL
launcher, which executes Linux-side) precedes per-user Git Bash installs on
default PATHs. Order matches so %SystemRoot% entries are only a last
resort.
…ace on win32

The built-in Herdr reporter connected to the exported unix-style socket
path verbatim; on Windows a local-domain connection must target
\\.\pipe\, and the reporter swallows connect errors by design, so the
mismatch was invisible. Map unmapped paths into the pipe namespace before
dialing.
…rt TTL

getWindowsProcessStartId spawns a synchronous powershell query that blocks
the event loop, and supervisor liveness checks plus daemon-ps poll loops
call it repeatedly for the same pid. Memoize successful ids for 5s so hot
paths reuse one query.
…adline

connectWorker granted every hello/worker_auth attempt a fixed 1s clock, so
a handshake that consistently needs longer (slow win32 boxes) failed every
retry regardless of the 30s outer budget, and the adopt/recover paths
shrank even the outer budget to 1.5-2s. Per-attempt waits now consume the
remaining outer deadline, all connectWorker callers use the one 30s
constant, and the downward 1s hello/auth overrides in daemon-mode and the
routed client fall back to the client defaults.
…pers

windowsHide defaults to false, so console-subsystem children of a
windowless daemon or detached worker allocate a fresh console and flash or
steal focus on every spawn; only one call site in the repo set the flag.
Add spawnHidden/spawnSyncHidden/execSyncHidden/execFileHidden/
execFileSyncHidden to utils/child-process.ts and migrate all 49
non-interactive coding-agent spawn/exec sites; the tui package's two
background spawns set windowsHide inline. Interactive spawns (editors,
inherit-stdio installers and relaunches) intentionally keep their console.
Comment thread packages/coding-agent/src/core/session-lease.ts Outdated
Comment thread packages/coding-agent/src/core/session-lease.ts Outdated
Comment thread packages/coding-agent/src/utils/child-process.ts
Comment thread packages/coding-agent/src/utils/child-process.ts
The per-pid cache could hand a recycled pid the dead process's start id
for up to 5s. The start id exists solely to detect pid recycling, and its
consumers act on that identity: the orphan-process journal decides
taskkill /T from it and daemon-ps shutdown waits poll it for change, so a
stale match can kill a replacement process tree or mask an exited owner.
No caller is both hot and staleness-tolerant; the handshake latency issue
is already addressed by deriving budgets from the outer deadline.
Comment thread packages/coding-agent/src/cli/owned-session-worker.ts
…vely

Windows pipe names are case-insensitive, but the pass-through check was
case-sensitive, so a valid \\.\PIPE\... target was re-joined into a
different pipe name and the error-swallowing reporter failed silently.
Lowercase only the prefix check; the returned path keeps its casing.
… into fix/windows-platform-seams

# Conflicts:
#	packages/coding-agent/src/core/kernel/repl-manager.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cc11f4e. Configure here.

Comment thread packages/coding-agent/src/utils/child-process.ts
… pins

Collapse the wrapper doc block to one line, drop test comments that restate
assertions, share one recording wrapper in the child-process mock, unwrap
single-test describes, and remove one posix assertion already covered by
its sibling. No behavior coverage removed; all pins keep their failing
assertions.
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