feat: add native Windows support - #1982
Conversation
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): 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>
Preserve canonical main helpers and reconcile native Windows rollout. Fix the inherited passivation message lookup race with a deterministic regression. Refs #1982.
| try { | ||
| const args = ["/PID", String(pid), "/T"]; | ||
| if (signal === "SIGKILL") args.push("/F"); | ||
| const taskkill = spawnHidden("taskkill", args, { |
There was a problem hiding this comment.
🟠 High utils/child-process.ts:183
signalProcessGroupOrProcess can terminate an unrelated process after pid is reused. The detached, unawaited taskkill runs after the caller's liveness check, so the original target may exit and Windows may assign the same PID before taskkill /PID <pid> /T /F executes; its tree is then killed instead. Use an identity-preserving Windows termination mechanism (such as a process handle or Job object) rather than deferring a PID-only kill across this asynchronous gap.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/utils/child-process.ts around line 183:
`signalProcessGroupOrProcess` can terminate an unrelated process after `pid` is reused. The detached, unawaited `taskkill` runs after the caller's liveness check, so the original target may exit and Windows may assign the same PID before `taskkill /PID <pid> /T /F` executes; its tree is then killed instead. Use an identity-preserving Windows termination mechanism (such as a process handle or Job object) rather than deferring a PID-only kill across this asynchronous gap.
Evidence trail:
3f1e444: packages/coding-agent/src/utils/child-process.ts:170-195; 3f1e444: packages/coding-agent/src/cli/daemon-ps.ts:1071-1093; 3f1e444: packages/coding-agent/src/core/exec.ts:75-90; Microsoft taskkill documentation: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill; Microsoft Process Handles and Identifiers documentation: https://learn.microsoft.com/en-us/windows/win32/procthread/process-handles-and-identifiers
Preserve child-ID ambiguity and passivation during message lookup. Verify deferred Windows termination identity and report final kill failures. Exercise rendered installers and document runtime requirements.
|
Addressing the Windows minimum-version review: Addressed in 90d3f18 by stating Windows 10 version 1809+ (and Windows 11) in the setup guide. This is the minimum documented by the exact pinned Bun 1.4.0 tag. Systems without IsWow64Process2 are already below that runtime minimum, so an API fallback would not make the application supported there. The architecture query is unchanged; no unsupported-version compatibility path was added. |
Add phase budgets and cooperative cleanup without changing termination code. Run native Bun and Node probes independently after prerequisite setup.
Compare fixed sentinel launches without changing production termination. Preserve bounded functional, caller-exit, quoting, and visibility evidence.
Keep PowerShell attached to the test broker and preserve caller-exit evidence. Exercise the same fixture through the existing compiled Windows executable.
Record the sentinel console PID and classify complete successful windowless queries. Run independent artifact and installer steps after successful native builds.
Remove added Node compatibility and retain Bun Windows identity checks. Preserve installer byte-content handling and bounded Bun startup coverage.
Deduplicate the upstream passivation-race fix and its release note. Preserve the Bun-only Windows corrections and installer coverage.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f812bdf. Configure here.
Kevin takeover — all prepared work is now on GitHubThis completes publication for handoff, not full-stack readiness. No prepared implementation remains only in a local worktree.
Remaining plan / TODO
Evidence boundariesThe earlier f812 Windows native job passed NUL startup for pinned Bun and the actual compiled app, PowerShell5.1 installer E2E, native build/version/help and broker controls. It still failed caller-exit kill, exec cancellation and identity mismatch; terminal/CPython stages were skipped. Those results motivated the published repair but do not prove it. Arm64 has cross-compile/PE-inspection evidence only, not native execution. The new uninstall-host assertion was not covered by that earlier installer pass. One informational Scope stays Bun → Windows → Windows2, Bun-only. No Node compatibility lane, Koffi upgrade, extra framework or new feature work. Keep canonical persistence/locks/ownership, recovery/deletion, auth/accounting, shell trust/settings security and CPython batch rejection. Ordinary merges/fast-forward pushes only; no force-push/rebase, PR approval/merge, host install/reload or release action was performed. Work is now handed off; no further edits or fix rounds will start without a new request. |

Kevin handoff — published work, not merge-ready
The previously local six-file repair is now published in
9282f0e8fd59fed3575aaecde195bae9678b827d(parentf812bdf3).noUselessStringRawremains. No claim that these Mac checks prove Windows behavior.Kevin's remaining TODOs
bf8894afa55832f7cfa2094c8a0d041bc680a691; handoff snapshot main is4ec05a0969825a32261ffed43ab5740071a73ae6(fix(ai): build from the committed model catalog instead of refetching it #2038 committed-catalog builds, fix(coding-agent): always render inactive sessions in the agents view #2091 inactive agents view, default prime inference to glm 5.3 #2123 GLM 5.3 default). These are intentionally left for takeover, not another fix round here.Windows2 is now published at
e1ebab8d389c60283c58f9e6aa7c2675efe5cd41. Full Kevin handoff and remaining TODOs. Native Arm64 execution remains untested. No PR approval or merge has been performed.Stack
Review in order: #1970 → #1982 → #1994. This PR integrates the Bun parent through ordinary history-preserving merges. #1994 removes superseded Windows fixes and keeps the remaining hardening. Package renaming and sandbox work are separate.
Windows support
Message lookup correction (from Bun #1970)
A deterministic test reproduced an inherited passivation race: a child could disappear while its replacement was not yet published. The current fix resolves resident and saved candidates before delivery, rejects ambiguous child IDs across parents, and briefly reserves an idle resident during lookup. Direct session-ID/name precedence, family authorization, admission, accounting, and delivery remain unchanged. The original persisted-passivation regression is unchanged; a separate real ephemeral-parent regression covers the new lookup window.
Validation
Earlier Bun-only correction:
f812bdf3eaaceb75c8c20a808e64b04d19e71172, tree4d0a033ee925fdd9ed84f69c4565e35180a95ab3. Includes Bun through53531749de6a157d4ab615c1d1941e809516b20fusing ordinary merges.byte[]as UTF-8 before the unchanged strict version validator. Preserve checksum, invalid-marker and stable/beta installer checks.1.4.0+34cbb9a40checks passed: read-only Biome/type/installer/browser checks and normal hooks (1,005 files, no fixes). The previous 13 mocked identity tests and construction check are reused by unchanged source hashes. This is not native Windows proof or a Node-excluded whole-build claim.f812bdf3Windows x64 CI failed: 54 behavior tests passed, six skipped, and the caller-exit kill and exec-cancellation cases failed. The identity fixture still returned zero instead of the expected mismatch code three. The minimal Bun-only signal-launch repair is now published above; its native result is separate.Historical diagnostics
The Node-specific experiments below are retained as history only. Their compatibility requirements and the proposed Koffi upgrade were withdrawn; they are not part of the current Bun-only plan.
Published diagnostic correction:
b0c9d868ae350924a61aeef4d6b6194175f371f3. Only the existing launch fixture and CI workflow changed. Production and dependencies remain unchanged.b0c9d868, Windows CI failed. Bun and the actual compiled Windows executable passed the full hidden-startup/caller-exit sentinel probes with the new PID-field evidence. Node still failed original D-ignore with0xc0000409; D-stderr passed. These are launcher/sentinel results, not production identity or whole-tree termination acceptance.--version/--helpsmokes passed. Arm64 was not executed. PowerShell 5.1 installer end-to-end ran but rejected byte-valued stable-version content before installation. The default release workflow setstext/plain; this failure demonstrates a mirror/response-representation weakness, not a default-host outage.Earlier runtime-broker evidence
Earlier runtime-broker diagnostic:
acf899ab82661b89a0b8b5c20175622ec81dbddd. This changes only the existing launch fixture and CI workflow. Production remains unchanged from90d3f184.unref()/process.exit(0)case remains required. A separate stderr-recording case cannot excuse its failure. Hidden startup requires native console evidence and the complete working window observer; missing evidence cannot pass.BUN_BE_BUN=1re-entry on the unchanged compiled Mac artifact. None of these results proves Windows behavior.acf899aWindows CI failed. The native app built and all three runtimes completed the broker sentinel/space-path cases. Bun and the actual compiled executable passed both caller-exit cases. Node’s original stdio-ignore caller still exited with0xc0000409; its passing stderr-recording variant does not excuse that failure. Visibility remains inconclusive: the native console API returned a positive process count rather than the probe’s expected no-console result. No production launcher, hidden-startup or real tree-termination acceptance is claimed. CPython, installer end-to-end and normal executable smokes were skipped. The full stack is not ready.Earlier launch-probe evidence
Earlier launch probe:
3d4aeb1753b9a0a0d1fd49437d33391c4040fd68. This adds two test fixtures and CI steps. Production remains unchanged from90d3f184.1.4.0+34cbb9a40and Node 22.23.2 construction checks and Node bundle/syntax checks passed. Repository check and normal commit hooks checked 1,006 files with no fixes. A test-only library-compatibility error was corrected without changing compiler settings. These local checks do not establish native PowerShell/C# or Windows behavior.3d4aeb1Windows CI failed. Under both Bun and Node, identical detached PowerShell launches returned zero without executing the sentinel; non-detached controls completed with the expected exit 37. The cmd candidate executed correctly but opened visible terminal windows, so it was rejected. Bun passed the caller-exit sentinel case; the Node caller exited with an unexplained0xc0000409, not success. No production workaround is accepted; the full stack is not ready.Earlier diagnostic evidence
Diagnostic follow-up:
30dfa53a1996724ceae8bbdf067cf61c621018f5. This changes five test/CI files only; production, dependencies, installer and documentation are unchanged from90d3f184.90d3f184native Windows job failed at two five-second outer test deadlines. Its later native Bun/Node fixtures and installer steps did not run. Other platform test jobs passed. The caller-exit failure is not yet explained; the exec test could not reach its existing abort-plus-five-second escalation within that outer deadline.1.4.0+34cbb9a40: changed test files 10 passed, two native-Windows tests skipped on macOS. Repository check and normal commit hooks checked 1,004 files with no fixes. Four cooperative-cleanup cases passed under Bun and Node 22.23.2 on macOS; bounded-observation controls and the Node-target fixture build/syntax check passed. None is native Windows proof.90d3f184: full daemon-mode file 210 passed and focused message regressions 11 passed. Its earlier process/exec result was 23 passed with two Windows skips, not a rerun of the changed diagnostic tests. The native executable build is reused because compiled-code inputs are unchanged; documentation sidecars were verified on90d3f184and remain unchanged.3f1e444f: all 374 local coding-agent file selections passed (5,098 tests, 72 skipped); kernel integration 23, process stress 19, and compiled artifact smoke 5 passed. TUI 786 and Python runtime 290 are retained unchanged-source evidence, not reruns for this diagnostic follow-up.30dfa53native Windows CI failed. Both Bun and Node returned native creation-time values, but the mismatch helper exited zero instead of three and wrote no trace file. The legacy process trees remained alive until cooperative cleanup. Later CPython, installer and executable steps were skipped. These are failures, not successful termination evidence; diagnosis continues and the full stack is not ready.Platform limits
ALL_ACCESS, so restrictive process DACLs can refuse termination even wheretaskkillalone could work. Verification/helper failures never fall back to a PID-only kill.execCommandreports final termination failure and continues watching/draining the live child; other shutdown callers retain their existing best-effort accounting.taskkillchild survives.pwshis unavailable.Earlier Windows evidence
The pre-follow-up
3f1e444fpassed native Windows CI with Bun1.4.0+34cbb9a40. Original results in run 33606586146 and old-source run 34063359243 are historical. None substitutes for updated-head validation.Refs RES-1268.
Note
Add native Windows support with PowerShell installer and process-tree signaling
install.ps1PowerShell installer supporting version/channel selection, update, uninstall, SHA-256 verification, and per-user PATH management.signalProcessGroupOrProcess,killProcessTree) now verifies process identity via PID and creation time before recursivetaskkill; daemon named pipes are scoped per-user with filesystem lease locking.signal.pthread_killschedules SIGINT on the main thread.execCommandtimeout/abort on Windows returns exit code 1 withkilled: falsewhen forced tree termination fails while the child remains alive; daemon pipe names changed from a global fixed name to per-user scoped names.Macroscope summarized 9282f0e.
Note
High Risk
Changes process-tree termination semantics, daemon IPC locking, and the public install/release pipeline on Windows; mis-signaling or installer/PATH bugs could affect user machines and long-running agent processes.
Overview
Adds native Windows support end-to-end: a new
install.ps1flow (checksum-verified ZIP download, versioned install under%LOCALAPPDATA%, userPATH, update/uninstall), plus Windows x64/Arm64 compiled binaries packaged as.zipreleases with rendered stable/beta PowerShell installers published alongside existing shell installers.Build/CI/release now compile and upload Windows artifacts, accept
.zipuploads with correct content types, and gate the aggregate CI job on a largewindows-latestjob (behavior tests, kernel bootstrap, native binary smoke, installer e2e, PE inspection).Runtime behavior on Windows replaces naive
taskkill/PID kills with creation-time–verified process-tree signaling (windows-process-signal.ts+ Bun helper re-entry), wired throughexecCommand,killProcessTree, and tests; failed termination can settle askilled: falsewhile still waiting on the live child. Daemon coordination uses per-user named pipes with filesystem lease locks and in-use probes instead of skipping locks on Windows.Also includes uv bootstrap via PowerShell, CPython interrupt via
_thread.interrupt_mainon Windows, TUI VT input via Bun FFI (koffi fallback), expanded docs, and extensive Windows-focused tests/fixtures.Reviewed by Cursor Bugbot for commit 9282f0e. Bugbot is set up for automated code reviews on this repo. Configure here.