feat(windows): support native Windows via a per-pid named pipe - #21
Merged
Merged
Conversation
Node's net module has no real AF_UNIX support on Windows: the local domain there is a named pipe, which must live under \\.\pipe\, never an arbitrary filesystem path (nodejs/node#55979). Claude Code's own official docs confirm it uses exactly this on native Windows, alongside one real protocol difference from macOS and Linux: the auth line is required there, not optional, and a connection whose first line isn't a valid matching auth line is closed without delivering anything. socketPathForPid now branches on process.platform (an explicit socketDir still always wins on every platform, unchanged precedence), producing \\.\pipe\cc-peer-<pid> with no directory of its own to create - start() skips its mkdir call on Windows accordingly. pidFromSocketPath detects a pipe path by its own shape rather than the current platform, since parsing should work on whichever kind of path it is actually given. Windows has no `ps`, so process-start verification needed its own adapter: WinProcInfo drives PowerShell's Get-Process for lstart(), sharing the existing signal-0 alive() probe and per-pid cache/in-flight-dedup logic with PsProcInfo (both now built on a shared CachedPidCommand rather than duplicating that machinery). The exact lstart string format is a cc-peer convention, not a reproduction of a real Windows Claude Code session's own format, which is not publicly documented - self-consistent for cc-peer's own entries, which is what roster admission actually needs for a peer this SDK created. Also fixes a real, unrelated bug this work surfaced: pidDomain was hardcoded to "darwin" regardless of the actual runtime platform.
Every other Windows-specific test in this package mocks process.platform on a POSIX runner, which proves the branch logic but cannot prove the OS actually accepts a named-pipe path from Node's net module the way those tests assume. This file skips everywhere except a genuine win32 process (describe.skipIf), so it exercises the real default socket path, the real PowerShell-backed proc-info adapter, and the real required-auth-line enforcement only when actually running on Windows.
…indows job Windows was excluded from the SEA release matrix because the runtime crashed on startup - not a CI misconfiguration but a genuine Node limitation (no filesystem-path AF_UNIX support on Windows) that the prior implementation never accounted for. Now that CcPeer branches to a real named pipe on native Windows, both windows-latest and windows-11-arm legs build a working, functional binary again. verify-windows runs on every push and PR, not gated behind release: a Windows regression should fail the same gate any other regression does, rather than surfacing only after a release already shipped it. It runs the plain test suite (not the coverage/mutation gate, which already runs once on Linux in verify) specifically to exercise test/windows-integration.test.ts's real, unmocked named-pipe coverage under an actual Windows kernel.
The rebase onto origin/main (which had removed Windows from the sea matrix in an earlier, since-superseded commit) dropped the re-addition this branch's own verify-windows commit assumed was already present - restoring it explicitly here. Also rewrites the readme's "no native Windows support" limitation, which the rest of this branch's work makes false: it now describes the real, still-true constraints (named pipe instead of a socket, a required auth line, an unverified procStart format) rather than a blanket unsupported claim.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…lesystem path An explicit socketDir was made to win on every platform, including Windows, where socketPathForPid then returned a literal filesystem path such as "/tmp/xyz/1234.sock". Node has no filesystem-path AF_UNIX support on Windows at all, so net.Server.listen() rejected every one of these with EACCES regardless of which directory was named, breaking the bulk of the test suite (and any real caller supplying a socketDir) under a genuine Windows kernel. socketPathForPid now always returns a named-pipe path on Windows, and folds a caller-supplied socketDir into the pipe's own name (a short hash) rather than treating it as a real directory, since a pipe has no directory of its own to keep separate callers apart. pidFromSocketPath parses the pid from the final "-"-delimited segment so it round-trips either shape. Also fixes a WinProcInfo unit test that assumed powershell.exe is never on PATH: true on every POSIX runner this suite otherwise runs on, but false on the real Windows CI runners this branch is meant to support, where PowerShell is genuinely resolvable regardless of PATH contents.
Two file-transfer tests assumed POSIX permission-bit semantics that NTFS doesn't have: a written file's mode reads back as an actual owner-only 0600 (NTFS has no such bit; writeFile's mode option only ever toggles the read-only attribute), and chmod(0o000) makes a file genuinely unreadable (on NTFS it does not, so the file the code under test opens stays readable and the expiry branch never triggers). Both are guarded with test.skipIf(win32); the file-transfer code itself needs no change; POSIX runners still exercise both cases in full.
…e-transfer validation
materialiseAttachment checked "starts with /" to decide whether a
descriptor's path was absolute before trusting it. A path staged on
Windows is drive-letter-rooted (C:\...), so every real attachment
received a "invalid transfer path" refusal there regardless of
validity. Switched to node:path's isAbsolute, which the reference
receiver's own contract ("an absolute path") actually means.
Several test fixtures assumed things that don't hold on a real Windows kernel: - Raw net/UdsTransport fixtures built literal filesystem paths (e.g. join(home, "lifecycle.sock")) directly rather than going through socketPathForPid, so they tried to bind a POSIX-shaped path Windows cannot use at all. A new shared testSocketPath helper builds a platform-appropriate path the same way socketPathForPid does, reusing the pid-agnostic fixtures needed here. - PsProcInfo's own describe block spawns a real ps and assumes POSIX signal-0/init-pid semantics; it never runs on Windows in production (CcPeer.create() only selects it when !isWindows()), so it is now skipped there — WinProcInfo already has its own coverage. - The foreign-token-tolerance and create()-selects-WinProcInfo tests encode POSIX-only protocol behaviour (Windows requires a valid auth line; WinProcInfo's own powershell.exe genuinely exists on a real Windows runner), both already covered by dedicated Windows-specific or real-Windows-integration tests elsewhere. - A key-file permissions test relied on chmod(0o000) removing read access, which NTFS does not do.
Adds --fix to the cached eslint invocation so a plain pnpm lint repairs auto-fixable violations instead of only reporting them, matching how lint-staged already runs eslint --fix on commit.
A real, non-DI CcPeer.create() spawns a genuine child process to read its own start time; unlike the dependency-injected fixtures elsewhere, there is no fake procInfo to short-circuit that cost. windows-11-arm CI runners were observed exceeding vitest's 5000ms default here, most plausibly from an x64-under-emulation PowerShell cold start. Introduces a shared, named timeout constant and applies it (per test or, where every test in a suite needs it, at the describe level) to every test that exercises a real CcPeer.create(), replacing the existing ad hoc 10_000/15_000/20_000 literals with one consistent value.
…volume Creating, timing, stating and removing on the order of 200 individual files sequentially issues far more filesystem syscalls than vitest's 5000ms default timeout was ever sized for. A windows-latest CI run exceeded it here even though the identical code path stayed well within it on the same run's ubuntu and windows-11-arm legs, most plausibly NTFS latency under CI disk contention rather than anything platform-specific to fix in the code itself.
|
🎉 This PR is included in version 1.2.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Claude Code does support native Windows — it uses a per-session named pipe there rather than a Unix domain socket, confirmed directly against the official docs, with one real protocol difference: the auth line is required on native Windows (optional on macOS/Linux). This was previously misdiagnosed as a platform we should exclude entirely (a prior PR dropped Windows from the SEA matrix after the binary crashed on startup); the real fix is implementing the actual transport difference, not excluding the platform.
Test plan