Skip to content

fix: harden native Windows runtime - #1994

Open
sethkarten wants to merge 13 commits into
feat/windows-supportfrom
feat/windows-hardening
Open

fix: harden native Windows runtime#1994
sethkarten wants to merge 13 commits into
feat/windows-supportfrom
feat/windows-hardening

Conversation

@sethkarten

@sethkarten sethkarten commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR hardens native Windows support after #1982. It keeps Git Bash as the default shell and does not use WSL.

  • prevents background daemon, worker, catalog, and update processes from flashing console windows
  • gives slow Windows worker launches a bounded 90-second supervisor handshake and a 120-second client create timeout
  • makes settings, session leases, kernel bootstrap locks, and RLM display tombstones survive common Windows sharing violations
  • keeps RLM deletion durable and retryable without duplicate terminal notices or late state resurrection
  • executes .cmd and .bat shims through a controlled cmd.exe command string with exact metacharacter tests
  • forces UTF-8 mode for the persistent CPython process on Windows and keeps CPython 3.11
  • selects only canonical Git for Windows installs by default; nonstandard installs require shellPath
  • adds Windows named-pipe support for Herdr, BOM-tolerant frontmatter, native tilde path handling, and raw multiline paste handling
  • gives persisted sessions an owned scratch directory through PRIME_AGENT_SESSION_TMP, TMP, TEMP, and TMPDIR

Safety boundaries

  • no WSL, Cygwin, or MSYS2 fallback from PATH
  • no shell: true
  • no guessed cleanup of user files
  • no IPython, Jupyter, ZeroMQ, or ipykernel
  • foreground interactive consoles remain visible
  • macOS and Linux keep their existing worker deadline and retry cadence

Local validation

  • ~/.bun/bin/bun run check
  • 208 Windows-hardening behavior tests in the CI test set: 208 passed, 2 Windows-only tests skipped locally
  • focused RLM lifecycle set: 4 passed
  • full RLM/display/daemon set: 306 passed
  • daemon named-pipe lifecycle and batch-shim set: 3 passed, 1 Windows-only test skipped locally
  • settings: 45 passed
  • session leases and recovery journal: 24 passed
  • terminal input: 67 passed
  • real persistent CPython/ACP kernel: 4 passed
  • git diff --check

Native Windows validation

Final run: https://github.com/PrimeIntellect-ai/prime-agent/actions/runs/33650350794

  • all 14 PR checks passed
  • Windows behavior: 200 passed, 6 platform-specific skips, 0 failed across 19 files
  • RLM deletion/finalization races: 4 passed
  • real supervisor-to-worker named-pipe lifecycle: passed in 8.15 seconds
  • terminal/TUI: 224 passed
  • persistent CPython at D:\a\_temp\Prime Agent ø\kernel-venv: 19 passed, 2 skipped
  • Win32 job and interrupt runtime: 25 ran, 1 expected skip, result OK
  • x64 executable PE machine: 0x8664
  • Arm64 executable PE machine: 0xAA64
  • PowerShell installer install, version 0.9.1, update, and uninstall: passed
  • compiled pi.exe --version (0.9.1) and pi.exe --help: passed

Stack

Base: feat/windows-support from #1982. This PR does not modify #1982 and should be reviewed after it.

Current stack integration

Ordinary merges include the updated Windows-support base. The daemon launch resolution retains launcher-aware subprocesses and windowsHide. Root checks passed. Isolated Linux Docker validation passed 445 targeted tests and 19 process stress tests, with one Windows-only case deferred to the native runner. Native Windows CI is rerunning on the updated head.

Related tracking: RES-1268 covers the overlapping Windows runtime and kernel compatibility work.

Note

Harden Windows runtime: bootstrap locks, shell selection, batch shims, and rename retries

  • Rewrites acquireBootstrapLock to atomically publish UUID-named candidate directories, reclaim stale locks through a proper-lockfile guard, and return an idempotent release that only removes a lock still owned by the current process
  • Reworks acquireSessionLease to fail closed on unreadable or corrupt owner files, retry candidate-vanishing races, and quarantine stale lease directories before deletion
  • Routes Windows .cmd/.bat Python shims through cmd.exe with arguments passed via validated environment variables, rejecting unsafe metacharacters
  • Limits Windows automatic shell selection to Git Bash in standard Program Files locations; removes PATH-based fallback for Cygwin/MSYS2/WSL
  • Adds bounded retry handling for transient Windows EBUSY/EPERM/EACCES rename failures across bootstrap locks, session leases, settings writes, and RLM display entries
  • Introduces a per-session scratch directory wired into RLM child environments (PRIME_AGENT_SESSION_TMP, TMP, TEMP, TMPDIR) and system prompt guidance
  • Adds raw multiline paste detection in tui.StdinBuffer so embedded-newline input without escape characters emits as a single paste event
  • Hides console windows for non-interactive Windows child processes across daemon, supervisor, and CLI launch paths
  • Risk: getShellConfig now throws on Windows when no Git Bash is found in standard locations, where it previously fell back to PATH-resolved bash; users with non-standard Git Bash installs must set an explicit shell path

Macroscope summarized 9063904.


Note

Medium Risk
Touches session leases, bootstrap locking, daemon worker handshakes, and RLM deletion persistence with behavior changes (fail-closed lease reads, stricter process liveness) that affect reliability on all platforms, not only Windows.

Overview
This PR extends native Windows support with runtime and durability fixes across the daemon, kernel bootstrap, session locking, and RLM lifecycle—without adding WSL/Cygwin/MSYS2 fallbacks from PATH.

Daemon and workers: Detached spawns set windowsHide on Windows so background daemons, workers, and catalog/update processes do not flash consoles. Worker connect uses longer budgets and exponential backoff on win32 (90s handshake, larger per-probe timeouts); create requests get a 120s default timeout on Windows only. Supervisor shutdown avoids clearing a replacement worker client if disconnect races shutdown.

Persistence and races: Settings saves use atomic temp+rename with Windows rename retries; session leases fail closed on corrupt owner.json, treat only ESRCH as a dead PID, and classify Windows rename contention when reclaiming. Kernel bootstrap locks are rebuilt (candidate rename, stale quarantine, proper-lockfile guard, transient rename retries). RLM display files retry Windows sharing violations, honor deletion tombstones (writers return false instead of resurrecting state), and deletion retries emit one failure notice per attempt.

Kernel/shell paths: Windows runs .cmd/.bat shims via cmd.exe with env-based argument passing and PYTHONUTF8=1 for venv/REPL; executable lookup respects PATHEXT. Default shell selection uses canonical Git for Windows paths only (explicit shellPath otherwise). Tilde expansion accepts ~\ on Windows; persisted sessions get a scratch tmp dir wired into kernel env and the system prompt.

UX/integration: Herdr state reporting maps socket paths to named pipes on Windows; frontmatter strips a UTF-8 BOM; TUI StdinBuffer treats raw multiline chunks as paste when bracketed paste is absent. CI adds a broad Windows behavior suite including named-pipe lifecycle and RLM race tests.

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

Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts Fixed
@sethkarten
sethkarten marked this pull request as ready for review September 6, 2026 21:51

@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.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts Outdated

@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 bde87f1. Configure here.

Comment thread packages/coding-agent/src/core/session-lease.ts
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts
Comment thread packages/tui/src/stdin-buffer.ts
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts
Comment thread packages/coding-agent/src/modes/daemon/rlm-subagent-display.ts
Comment thread packages/coding-agent/src/utils/shell.ts Outdated
Comment thread packages/coding-agent/src/utils/shell.ts
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts Outdated
Comment thread packages/coding-agent/src/core/kernel/bootstrap.ts
@sethkarten

Copy link
Copy Markdown
Contributor Author

Fixed the native Windows shutdown failure in 906390483. The disconnect callback could clear worker.client while shutdown awaited its response, causing worker.client.close() to throw and leave the supervisor running. Shutdown now retains the original client reference and only clears the worker field if it still references that client.

Validation:

  • Both normal/archive disconnect regressions fail on ce2d04f87.
  • Corrected isolated Docker tests: 40 passed, including the supervisor-worker lifecycle test.
  • Full isolated Docker process stress: 19/19 passed.
  • Root and pre-commit checks passed.

The failing Windows log is from https://github.com/PrimeIntellect-ai/prime-agent/actions/runs/34067874964/job/101579752358. Fresh native Windows validation is required on the new head. No installer, shell-discovery, or protocol changes were made.

@sethkarten

Copy link
Copy Markdown
Contributor Author

Native Windows passed on current head 906390483: https://github.com/PrimeIntellect-ai/prime-agent/actions/runs/34068337321/job/101580997854

Verified in the hosted log:

  • The previously failing supervisor-worker shutdown lifecycle now passes.
  • Old release handles cannot remove a newer lease; concurrent release calls share cleanup; a failed release can retry while retaining successful-release idempotence.
  • Real .cmd and .bat Python overrides accept the current runtime and reject a stale runtime.
  • Windows behavior, deletion races, CPython, PowerShell 5.1 installer, and executable smoke tests pass.

This is native Windows x64 execution; Windows Arm64 is cross-compilation and PE inspection only. All current functional CI and both review bots have passed. JavaScript/TypeScript CodeQL is still running; final readiness is not yet claimed.

@sethkarten

Copy link
Copy Markdown
Contributor Author

Final stack readiness audit passed.

PR Verified head Status
#1970 5d685c425 Mergeable; all latest checks pass
#1982 9680ac3df Mergeable; all latest checks pass
#1994 906390483 Mergeable; all latest checks pass

JavaScript/TypeScript CodeQL has completed successfully. Current-head native Windows, both review bots, root checks, isolated Docker behavioral tests, and all 19 process-stress tests passed. Full review threads, review summaries, and issue comments were audited; no unresolved actionable findings remain. Design-conflicting shell-discovery advice was rejected with the trust-boundary rationale rather than reverted.

Fresh fetch confirms current main → #1970 → #1982 → #1994 ancestry. All three worktrees are clean and match their published heads. Native Windows x64 evidence: https://github.com/PrimeIntellect-ai/prime-agent/actions/runs/34068337321/job/101580997854. Arm64 evidence remains cross-compilation and PE inspection, not native execution.

Only human approval remains. No PR was merged. Automated monitoring is being stopped.

sethkarten added a commit that referenced this pull request Sep 7, 2026
Import selective Bun-independent fixes from #1994 at
ce2d04f, relative to declared
#1982 base 9680ac3.

Preserve #2036 Windows seams and current Node runtime. Keep Bun,
installer, shell-policy rollout, scratch directories, and CPython
batch overrides outside this consolidation. #1994 remains partial.
Atomic writer and bootstrap-lock overlap awaits canonical #2035
reconciliation after parent stacks land.
sethkarten added a commit that referenced this pull request Sep 7, 2026
* fix(coding-agent): resolve the kernel venv python under Scripts on win32

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.

* fix(coding-agent): expand ~/ with the platform path separator

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.

* fix(coding-agent): prefer non-System32 bash over the WSL trampoline on 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.

* fix(coding-agent): dial the Herdr socket inside the named-pipe namespace 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.

* fix(coding-agent): cache the win32 process start id per pid for a short 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.

* fix(coding-agent): derive handshake budgets from the outer connect deadline

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.

* fix(coding-agent): route background spawns through hidden-window wrappers

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.

* chore(coding-agent): add windows-seams changelog fragments; tighten new comments

* test(coding-agent): align clipboard and login-dialog spawn assertions with hidden-window wrappers

* revert(coding-agent): drop the win32 start-id TTL cache

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.

* fix(coding-agent): match Herdr pipe-namespace prefixes case-insensitively

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.

* refactor(coding-agent): tighten windows-seam comments and consolidate 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.

* fix(coding-agent,tui): harden current-runtime Windows sessions

Import selective Bun-independent fixes from #1994 at
ce2d04f, relative to declared
#1982 base 9680ac3.

Preserve #2036 Windows seams and current Node runtime. Keep Bun,
installer, shell-policy rollout, scratch directories, and CPython
batch overrides outside this consolidation. #1994 remains partial.
Atomic writer and bootstrap-lock overlap awaits canonical #2035
reconciliation after parent stacks land.

* fix(coding-agent): normalize Windows bash candidate paths

---------

Co-authored-by: Seth <seth@primeintellect.ai>
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