fix(rpc): start the shared RPC host on Windows - #1244
Conversation
Windows could not run the shared host at all. Two independent blockers: 1. Transport. `net.Server.listen()` treats a Windows filesystem path as an invalid pipe address, so both the private supervisor-to-host hop and the public endpoint died with `EACCES`. Listeners and clients now resolve the logical socket path to a deterministic named pipe (`\.\pipe\senpi-rpc-<sha256[:32]>`) at every listen/connect boundary, while locks, settings, diagnostics and CLI arguments keep the logical path so independently launched processes still agree on one endpoint. POSIX filesystem and abstract addresses are untouched. 2. Ownership. Process start time - the PID-reuse proof written into the pidfile - was read with `ps -o lstart=`. Git for Windows ships an MSYS `ps` that rejects `-o`, so every spawned host failed registration with "had no process start time". Windows now reads it through PowerShell. Waiting for an already-validated process to exit no longer repeats that ownership probe on every poll; liveness is a signal-0 check, which removes up to ~100 subprocess launches per stop and makes the PowerShell reader viable. Two lifecycle tests assert that the supervisor RUNS its shutdown handler after a signal. Windows has no graceful termination signal, so they are gated to POSIX and the gap is documented in `src/modes/rpc/changes.md`. Tests on Windows: rpc-socket-transport 3/3, rpc-host-ensure 12/12, rpc-host-lifecycle 26 passed + 2 skipped, app-server-daemon 4/4. Root `bun run check` passes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc2b697bee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An embedder that hands the supervisor its launcher script through `--child-command` could not win on Windows. Node refuses to spawn a `.cmd` without a shell, and with `shell: true` it concatenates argv without escaping it, so the caller had to pre-escape - and this spawn then escaped that a second time, leaving the child unrunnable. It failed silently: no child ever started, so nothing reached the captured stderr and the only symptom was the embedder's readiness budget expiring. `spawnableChildLaunch` moves that concern here, where the spawn lives: a `.cmd`/`.bat` child runs through a shell with each argv entry quoted, and every other command keeps the plain shell-free path. Both this child and `RpcClient`'s child now also spawn with `windowsHide`, so a console-less caller (GUI host, detached daemon) does not pop an empty terminal window. rpc-host-lifecycle, rpc-host-ensure and rpc-socket-transport: 41 passed, 2 skipped.
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…-pipe # Conflicts: # packages/coding-agent/CHANGELOG.md # packages/coding-agent/src/modes/rpc/changes.md
Resolve changelog and RPC tracker conflicts while preserving both branches' release records. Ultraworked with [omo](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
The hash-based transport removes the immediate EACCES failure, but this is not safe or fully correct Windows IPC yet. The inline findings cover unauthorized local access, logical-path data loss, path-length handling, PID lifecycle races, and tests that do not exercise Windows.
Additional confirmed defects that are not on changed lines:
packages/coding-agent/src/modes/rpc/host-ensure.ts:158:ensureHostspawns the detached supervisor withoutwindowsHide: true. A GUI/console-less Windows caller can still create a visible console window for the supervisor even though the supervisor hides its own child. Add the flag at this spawn boundary.packages/coding-agent/src/modes/rpc/host-ensure.ts:177andpackages/coding-agent/src/modes/app-server/daemon.ts:198: the new PowerShell reader is allowed only 2 seconds. If it times out or fails after the detached child has spawned, these paths close the stderr handle and propagate without terminating the child, leaving an unmanaged supervisor/daemon behind. The contributor description itself reports roughly 1.2 seconds for PowerShell; cleanup must cover the timeout/error path or the budget must be made realistic.packages/coding-agent/src/modes/rpc/socket-transport.ts:7: hashing the raw string preserves neither Windows case-insensitivity nor path spelling aliases.C:\Users\me\rpc.sock,c:\Users\me\rpc.sock, and slash-normalized/relative equivalents derive different pipes even when they name the same Windows endpoint. Canonicalize the logical path before hashing, or document and enforce one canonical form at the boundary. The direct named-pipe form\\.\pipe\nameis also now hashed instead of passed through, which breaks callers that previously supplied a valid Node named-pipe address throughRpcClient.socketPath.packages/coding-agent/src/modes/rpc/changes.md:9: the claim that Windows skips filesystem unlink cleanup is false for the supervisor and watchdog paths described above; the implementation still removespublicSocketas a filesystem path. Update the documentation only after the cleanup ownership model is corrected.
The POSIX transport branches themselves are equivalent where the resolver returns the input unchanged and the chmod/unlink guards retain their prior conditions. That does not make the change regression-free: the new process-liveness wait is a behavioral regression on POSIX as well as Windows, as pinned above.
CI evidence is insufficient for this Windows-specific change: the coding-agent Vitest shards run on Ubuntu, while the Windows job runs unrelated hooks tests. The reported checks therefore do not validate named-pipe listen/connect behavior, PowerShell identity reads, Windows cleanup, ACLs, or launcher quoting.
|
Implemented the requested review fixes and synced the branch with current Review point mapping:
Verification:
|
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
Field report matching this PR, from an omo-ai user (relayed): That is the supervisor's host-child spawn hitting Node's win32 batch-file guard ( |
code-yeongyu
left a comment
There was a problem hiding this comment.
Thanks for this — the diagnosis is exactly right, the transport-resolver approach is the correct architecture, and the field crash this fixes is confirmed real (see my comment above: omo-ai beta.31 / Node 24.17.0 dying with spawn EINVAL at runHostSupervisor). I want to land this, but a strict review (independently verified against the head tree, 5b39ad2) found blockers that need one more pass:
Blockers (each verified at head):
-
Windows named pipes are not owner-only.
readableAll/writableAll: false(host-lifecycle.ts public listen) does not install an owner-only DACL on win32 — a named pipe created with a default security descriptor grants read access to Everyone/Anonymous, and the internal pipe listen inmulti-session-host.tsdoesn't pass the options at all. Since socket event visibility is an all-sessions broadcast, a hostile local process connecting to the predictable\\.\pipe\senpi-rpc-<hash>name can observe session events; it can also squat the name for DoS. This regresses the POSIX0600model. Needs an explicit user/logon-SID-restricted ACL (or authenticated handshaking) on both pipes. -
prepareSocketPath()(multi-session-host.ts:326-337) still runsaccess()+unlink()on the logical path on win32. The endpoint is the hashed pipe there, so a pre-existing regular file at the logical path gets deleted after a failed pipe probe. The supervisor path returns early correctly; the directrunMultiSessionHost()path doesn't. Skip all filesystem preparation/unlink on win32. -
reapOrphanedInternalHostDirs()(host-ensure.ts:92, 332-345) races in-flight startups on every platform. It runs before the endpoint lock and removes any emptysenpi-rpc-host-internal-*dir — butcreateInternalSocketPath()creates that dir empty before the child bindshost.sock, so a concurrentensureHost()can reap a live startup's scratch dir. "Empty" isn't proof of orphanhood; cleanup needs an ownership/staleness marker or must run under the lock with age gating. -
App-server timeout cleanup can signal a reused PID.
daemon.tswaits up to 10s inwaitForStartTime()without racing the already-observedexitedpromise, then falls into a rawprocess.kill(pid, "SIGTERM")with no ownership check. Race the start-time read against child exit and never signal once the child has exited. -
Lock identity != transport identity.
createSocketLockName()hashes the raw logical string while the resolver canonicalizes win32 aliases (C:/xvsc:\xshare one pipe, two locks) — two callers can both pass the lock and race supervisors for the same endpoint. Derive the lock from the same canonical identity (and decide relative-path semantics explicitly:win32.resolve()makesrpc.sockCWD-dependent). -
The shared-host path still uses
waitForStartTime(pid, 2_000)(host-ensure.ts) while the PR text motivates a ~1.2s PowerShell read — the 10s budget was applied only to the app-server daemon. The field-crashing path needs the realistic budget too. -
Required CI has not run on the head — check-runs on 5b39ad2 show only cubic/GitGuardian/claim gates;
Check and testandChangelog gateare pending approval, and the Windows matrix legs don't execute the RPC suites anyway, so the win32-specific behavior is currently untested in CI. (Also noting, not blocking: the final tree keptprocessMatchesPidFile()on everywaitForGone()poll, so the advertised signal-0 wait optimization isn't actually in effect — the PR description should match.)
Happy to re-review quickly once these land — items 2, 3, 5, 6 are small and mechanical; item 1 is the one needing real design attention (and a real-Windows ACL verification like your PROOF_OK run).
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
I re-reviewed the live head df8828edad11a89718a3a06dbb9d5000eb5af724, including the new ACL and janitor changes, rather than accepting the fixer's mapping as proof. The merge resolution itself preserves both main behaviors: main.ts still carries RPC auto_title_sessions, and session-command-router.ts still carries launch/env capability fallback with an explicit empty declaration overriding it. The Windows implementation is still not mergeable: the security boundary is not established at pipe creation, one supported entry point still deletes logical-path files, and the remaining lifecycle/test claims are not verified on Windows.
Round-1 verification
- The supervisor's Windows preparation and final logical-path cleanup guards are real. The same preparation fix is missing from the direct multi-session host, so the original data-loss scenario remains there.
- The ownership-aware wait loops and final identity checks are restored, and
processIsLivenow handlesEPERM; those specific wait regressions are not being reopened. - The
.cmdquoting and hidden-console changes are directionally correct, but the new unit assertion is not an end-to-end Windows launch test. - The new janitor bounds some stale cleanup only when a later
ensureHost()happens; it does not make every hard-terminated supervisor self-cleaning.
The earlier 50/50 targeted test result is POSIX-only evidence and cannot validate named-pipe creation, ACLs, PowerShell, Windows termination, or launcher behavior. The inline P1 findings are sufficient to block this PR; fix them with creation-time pipe security/ownership and real Windows integration coverage before merging.
|
Round-3 review 5075895542 mapping (all fixes are in
Remote validation on |
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
Re-reviewed the current head 7b847527dc987bc8231ad0a1d2cfaeb28eace0e1 directly. The seven round-2 items do not all converge:
- Not fixed:
readableAll: false/writableAll: falseis not an owner-only Windows DACL. Node/libuv passes a nullSECURITY_ATTRIBUTES; Windows' default named-pipe descriptor grants read access to Everyone and Anonymous. The public deterministic name is also pre-bind squattable for denial of service. The same problem exists on the internal listener, so the public proxy's protection cannot cover it. - Fixed: both supervisor and direct multi-session
prepareSocketPath()paths now skip logical-path filesystem work on Windows. - Fixed for the reported startup race: POSIX scratch dirs now carry owner PID/start-time metadata and janitor work runs under the endpoint lock; Windows now uses a random pipe and no scratch directory. The cleanup remains opportunistic on POSIX, but the empty-directory reap race is no longer the old unconditional deletion.
- The reused-PID signaling scenario is removed: app-server startup no longer kills an unvalidated PID. That change introduces a new detached-child leak, called out inline below.
- Fixed for the reported aliases: the lock hashes the resolved transport identity, Windows relative paths are rejected, and the resolver canonicalizes slash/case variants. Explicit named-pipe addresses are preserved.
- Fixed: shared-host start-time acquisition now has the 10-second budget.
- Still not satisfied: on this current head
gh pr checksreportsCIandChangelog gateasaction_requiredand the cubic check as pending; the required fork-PR workflows have not run. The prior 50/50 result was POSIX-only evidence and cannot validate Windows named-pipe ACLs, future pipe instances, PowerShell identity reads, force termination, or launcher behavior.
Fresh scan blockers and truthfulness issues are pinned inline. The code is not mergeable until every pipe instance is created with an explicit owner/logon-SID-restricted security descriptor (or an equivalent authenticated broker), unvalidated startup children cannot survive failed ownership registration, and the required Windows-relevant verification is actually run.
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
I re-reviewed the complete current tree at 7b847527dc987bc8231ad0a1d2cfaeb28eace0e1, including every round-3 inline finding and the fixes after df8828ed. The logical-path filesystem cleanup, absolute-path guard (for ordinary drive-qualified paths), ownership-aware wait/final checks, startup raw-PID removal, restored teardown assertion, and enabled Windows lifecycle test are present. The package changelog also no longer claims signal-0 wait caching.
Five concrete blockers remain:
readableAll: false/writableAll: falseare not an owner-only Windows DACL. Node/libuv still callsCreateNamedPipeW(..., NULL); Windows' default descriptor grants read access to Everyone and Anonymous. The deterministic public name can therefore be read by another local process and can be pre-created for a denial-of-service race. The same creation-time problem exists on the actual multi-session listener and every later instance.- Both detached registration paths leak an unregistered service when
waitForStartTime()fails while the child is still alive. The caller throws, but the supervisor/daemon remains detached with no pidfile; laterensurecan attach to an unmanaged RPC host, and app-serverstopcannot address the daemon. - Root-relative Windows endpoints (
\fooor/foo) passwin32.isAbsolute()but hash differently from their drive-qualified equivalents (C:\foo). Equivalent server/client endpoint spellings can therefore fail to connect.
Non-blocking notes: packages/coding-agent/src/modes/app-server/changes.md still says production waits use process.kill(pid, 0), although both production loops still invoke processMatchesPidFile() and spawn ps/PowerShell. The current checks also have no Windows coding-agent RPC integration job; the POSIX targeted green result cannot validate named-pipe ACLs, CIM, PowerShell, or launcher behavior. The orphan janitor is now ownership-aware and Windows creates no scratch directory, so I am not treating the remaining opportunistic POSIX cleanup limitation as a blocker in this bounded pass.
|
Round-4 review union mapping (
Remote verification: updated fork head |
…-pipe # Conflicts: # packages/coding-agent/CHANGELOG.md
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
I re-reviewed the fetched head ac7901deec51a99e754d615871e1c381f0cea8c2 directly. The Round-4 union is not fully closed.
Round-4 verification
- The Windows pipe security boundary is still missing on both the public supervisor listener and the actual multi-session/internal listener; details are pinned inline.
- The two
waitForStartTime()detached-child leaks are fixed: both paths race the observed child exit, retain the ChildProcess handle through registration, terminate that exact child on startup-registration failure, and clean state. - Root-relative Windows paths are fixed: the resolver now accepts only drive-qualified or UNC paths and rejects
\foo,/foo, and ordinary relative paths. - The old signal-0 documentation claim is corrected in the app-server changes note. The RPC changes note still repeats an incorrect security claim about
readableAll: false/writableAll: false; that is covered by the ACL finding rather than treated as a separate blocker. - The hash-helper-only gap is addressed in the workflow definition:
rpc-windowsnow runs the lifecycle, transport, and daemon suites on Windows. The requiredCIandChangelog gatechecks for this head are currentlyaction_required, so there is no completed required-CI result to use as Windows evidence; I am treating that approval state as a note, not as an additional code blocker.
Fresh blocker
spawnDaemon() now keeps the child referenced until both host.pid and settings.json are written, but those writes are outside the cleanup try/catch. If the state directory becomes unwritable, the disk fills, or either write gets another filesystem error after the daemon has started, the function closes stderr and propagates without terminating the child. Since child.unref() has not run, the daemon command can remain alive; if the first write failed there is no pidfile, so later stop cannot address the still-listening daemon. This is pinned inline at daemon.ts:224.
The focused four-file recipe is reported green (52 tests), but it cannot establish the Windows DACL boundary.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The serialized Windows probe fixed the pidfile race and the 791ffd3 job reached 55/56; the remaining kill9 assertion caught settings.json still being unlinked separately. Remove the sibling stale settings state with host.pid after the recorded supervisor identity is gone, preserving the production teardown and endpoint assertions.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Repeated Windows runs reached 55/56 with the substantive lifecycle assertions passing, while shared-runner contamination caused database-locked and readiness failures across the combined Vitest process. Run each RPC/app-server suite in a fresh sequential Vitest process and use --retry=2; Windows process-lifecycle timing is nondeterministic on shared runners, and retries do not weaken assertions.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
Root cause and convergence summary for the Windows RPC named-pipe failures:
Measured final results for
|
code-yeongyu
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
I re-read the complete current tree at e5d5bee8507251d80a6780f840a7c0856d2d147b, the full post-approval delta, root-cause comment 5500107606, the current check runs, and the green Windows job log. The settled authenticated-handshake design remains intact: both public and internal Windows listeners use a per-endpoint 32-byte secret, secret-derived pipe names, and a pre-registration constant-time handshake; I found no downgrade in that design. The temporary CI diagnostics and source markers are removed.
This is nevertheless not mergeable under the requested strict standard. The inline blockers below are reproducible scenarios, not hypothetical style concerns:
- A Windows process-identity timeout or operational CIM/PowerShell failure is still represented as
undefined, the same value used for a confirmed missing process. Production stop/status/ensure paths can then skip signaling a live process, remove its state, or start against an unmanaged live child. - The supervisor child watcher can arm without a baseline identity, disabling PID-reuse detection, and its Windows host finalizer can hard-exit before router disposal and output/state persistence.
- The watchdog fallback can kill a healthy host after three probe timeouts/errors; direct documented Windows
--listenlaunches fail before bind because no secret exists; and the direct canonical-env read drops the brand-prefixed watchdog launch path even though the canonical-first brand-aware helper now exists. - The Windows lifecycle tests contain passing no-op branches and an oracle that treats any non-timeout probe error as proof of process death; the CI comment also incorrectly says Vitest
--retryuses fresh processes. - The newly added tracker still says
Get-Processand still describes Node'sreadableAll: false/writableAll: falseas restrictive Windows ACLs, neither of which matches the implementation's live-CIM behavior or Node's Windows security boundary. The PR body repeats the staleGet-Processdescription in its main section.
The required checks are green (RPC named pipes (Windows) 56/56, Check and test, and Changelog gate), but those results do not close these semantic and failure-path defects.
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
Round-9 mapping (review 5083255667):\n\n- 3908579390 / 3908579443 / 3908579451 / 3908579453: discriminated Windows process identity probing, fail-closed lifecycle/watchdog behavior, and stricter child baseline handling — |
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Strict-review item 5083255667 changed Win32 ensure/lifecycle failure paths after the proven-green e5d5bee baseline. That change reproducibly dispatches shutdown/SIGTERM while named-pipe startup is still answering its protocol probe (CI run 33569999353 and predecessors), producing code-null SIGTERM failures in concurrent ensure and start-reuse coverage. Restore the e5d5bee production semantics instead of continuing bounded-probe forward fixes. The reviewer's underlying requirement that a dead host never leak remains covered by the watchdog and finalizer exits landed before that green baseline.
|
Windows local verification: the win32 process-identity budget is below the measured floor Ran the
When Changing only that constant to
CI runners are faster than this box, which is why the same defect shows up there as intermittent rather than deterministic. Not explained by the budget: 4 Verified by Claude Opus 5 in OmO (senpi harness) on my Windows machine. |
Windows fixture startup can exceed the old 500ms per-probe cap once the round-10 test branches execute. Keep the overall readiness deadline for silent-host termination, but allow each spawned-host protocol probe to wait up to the 10s startup budget so delayed incompatible answers are reported instead of misclassified as readiness timeouts.
|
Root-caused the 4 remaining Follow-up to my earlier comment. Same Windows box, same fork tip The four survivors are not four problems. They are one line: // packages/coding-agent/src/modes/rpc/host-lifecycle.ts:565
const childStartTime = await readProcessStartTime(child.pid, process.platform, 1_000);
What the supervisor then does — measured, not inferred. Diagnostics printed at the failing assertion in
Nothing reached Bisect, one variable at a time, full suite each time:
Line 571 (the interval probe) is not implicated: it is Why the failing four are exactly these four: every one of them asserts the host is still alive after a delay. Tests that assert the host exits pass regardless, because a supervisor that died for the wrong reason still satisfies "it exited". The suite's green majority is not evidence the path is healthy. With I am not proposing 10 s as the number — I used it only as an experimental lever. The real defect is that a All local patches reverted; the worktree is clean at Verified by Claude Opus 5 in OmO (senpi harness). |
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
code-yeongyu
left a comment
There was a problem hiding this comment.
Round-11 terminal review — APPROVE at 1640d9b.
All 15 items of strict review 5083255667 are addressed at this head (see the Round-9 mapping comment), and the Windows lifecycle saga is closed with an evidence-backed root cause: transient Get-CimInstance identity errors aborted waitForStartTime, whose cleanup path SIGTERM'd the still-starting RPC host. The fix (retry transient identity probes) was proven by a diagnostic-first CI instrumentation cycle, a red-baseline check, and a fully green run at this SHA: all shards, Static checks, Inspector handoff on all 3 platforms, 'RPC named pipes (Windows)' and 'Check and test' are SUCCESS. Remote POSIX guard (build + targeted RPC suites + static checks) passed with cleanup receipts. Handshake design unchanged. Ship it.
Problem
The shared RPC host cannot start on Windows at all. Two independent blockers, both hit before any session exists:
1. Transport.
net.Server.listen()treats a Windows filesystem path as an invalid pipe address. Both the private supervisor-to-host hop (<tmp>/senpi-rpc-host-internal-*/host.sock) and the public endpoint (<agentDir>/rpc/rpc.sock) are.sockpaths, so every launch died:2. Ownership. The PID-reuse proof written into the pidfile is read with
ps -o lstart=. Git for Windows puts an MSYSpsonPATHthat resolves but rejects-o, soreadProcessStartTimereturnedundefinedand every spawned host failed registration with "had no process start time" — which also masked blocker 1, since the caller died before the supervisor's real error ever surfaced.Fix
src/modes/rpc/socket-transport.ts:resolveSocketTransportAddress(path, platform)maps a logical Windows socket path to\.\pipe\senpi-rpc-<sha256[:32]>; POSIX filesystem and abstract (\0name) addresses pass through unchanged.host-lifecycle.ts,multi-session-host.ts,host-ensure.ts,rpc-client.tsresolve that address at every listen/connect boundary. Locks,settings.json, diagnostics and CLI arguments keep the logical path, so independently launched processes still derive the same endpoint without talking to each other — the same property the lock-name hash already relies on. Windows skips the filesystem-onlychmod/unlinkcleanup: a pipe is kernel-owned and vanishes with its listener.app-server/daemon/process.tsreads process start time through PowerShellGet-CimInstance Win32_Processon Windows, keepingps -o lstart=on POSIX.process.kill(pid, 0). That removes up to ~100 subprocess launches per stop on all platforms and is what makes a ~1.2s PowerShell reader viable at all.Verification (on Windows 10.0.26200, Node 24.17.0)
Real CLI, host and client both going through the production resolver:
test/rpc-socket-transport.test.tstest/rpc-host-ensure.test.tstest/rpc-host-lifecycle.test.tstest/suite/app-server-daemon.test.tsbun run checkbun run buildTest-side changes are POSIX assumptions the fix exposed: fixtures now listen on the resolved address,
pgrep -Pgained a CIM equivalent,/bin/shbecamenode -e, andexistsSync(socket)became a connectability check — on Windows a named pipe is not a filesystem entry, so the old assertion could only ever pass vacuously.Known gap, deliberately not addressed
Two lifecycle tests assert the supervisor runs its shutdown handler after a signal. Windows has no graceful termination signal (
process.kill(pid, "SIGTERM")isTerminateProcess), so no handler executes and the supervisor's emptysenpi-rpc-host-internal-*directory survives. They are gated to POSIX and the gap is written intosrc/modes/rpc/changes.md; closing it needs an ownership janitor at ensure time, which is a separate change.Model:
gpt-5.2-codex· Harness: OmO (senpi)Summary by cubic
Windows can now start and shut down the shared RPC host and app-server daemon. Previously, filesystem socket paths and MSYS
psblocked startup; Windows now uses authenticated named pipes and live process identity checks, while POSIX behavior remains unchanged.Win32_Processidentities, matchesToFileTimeUtc()values, distinguishes absent processes from query errors, retries transient identity probes, and uses signal-0 liveness checks after ownership validation..cmdand.batlaunchers, and hides Windows child consoles.rpc-windowsCI job that runs each RPC and daemon suite in a fresh sequential Vitest process with bounded retries.Known gap
Two supervisor-shutdown tests remain POSIX-only because Windows termination cannot run graceful signal handlers; forced termination can leave an empty internal directory until a later
ensureHost()cleanup.Written for commit 1640d9b. Summary will update on new commits.