feat: Windows 11 support — Claude Code (#46)#73
Open
Cannon07 wants to merge 12 commits into
Open
Conversation
Captures the design decisions from the Windows-support grilling session: - ADR-0007: shared PowerShell shim, all agents (incl. OpenCode) reuse it; supersedes ADR-0006's prediction that OpenCode would go shim-free in TS. - CONTEXT.md: Hook entry (per-OS, PowerShell 5.1 floor), Socket discovery (pipe-enumeration fallback), Pidfile (%LOCALAPPDATA% base on Windows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lands the cross-platform-safe portion of Windows support, ahead of the
PowerShell shim layer. All changes are either gated behind has("win32")
(dead code on Unix) or covered by the existing Unix E2E tests:
- pidfile.lua: write the pidfile under %LOCALAPPDATA% on Windows; the
Unix XDG/$HOME formula yields a driveless path there. Comment explains
why we don't use stdpath() (the shim reader can't call it).
- backends/codex.lua, copilot.lua: match installed hook entries by
adapter-script *stem* (code-preview-diff / code-close-diff) instead of
the full .sh fragment, so detection works for the .ps1 counterparts.
- backends/codex.lua, copilot.lua: gate chmod behind has("unix") — it's a
no-op on Windows, where the command invokes the interpreter explicitly.
- backends/codex.lua: honour $CODEX_HOME in global_config_path().
- health.lua: report PowerShell presence on Windows in place of the jq
check (the .ps1 shims use native ConvertFrom-Json).
Verified on macOS: Lua specs (110), codex E2E (22), copilot E2E (15) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First end-to-end Windows path: discovery + RPC primitives plus the Claude
Code shims and installer wiring. Mirrors the .sh layer one-for-one.
- bin/nvim-socket.ps1: named-pipe discovery (env var → pidfile under
%LOCALAPPDATA% → pipe enumeration), with the --remote-expr responsiveness
probe. No is-socket precheck (no reliable pipe existence test).
- bin/nvim-call.ps1: RPC over a named pipe. Writes the args JSON to a %TEMP%
tempfile VERBATIM (no ConvertTo-Json round-trip — ADR-0007 depth invariant)
and forward-slashes the tempfile path before splicing it into the luaeval
source (Windows backslashes are Lua escapes).
- backends/claudecode/code-{preview,close}-diff.ps1: the shims; abstain
(exit 0) on any failure.
- claudecode.lua: on Windows, write `powershell -NoProfile -ExecutionPolicy
Bypass -File <path>.ps1` into settings and resolve .ps1 paths. Unix path
unchanged (verified: claudecode E2E 15/15, Lua specs green on macOS).
UNVALIDATED ON WINDOWS — drafted on macOS. Two spike items to confirm on a
real box: Claude Code's invocation of the command field, and PS 5.1
--remote-expr argument quoting (see the comment in nvim-call.ps1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures in-flight state not in the ADRs/commits: the validation ladder (rungs 0-4) to run on a real Windows box, the two spike items, the held rollout, and the invariants to preserve. Lets a fresh Claude Code session on the Windows machine pick up after `git pull`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ran the ADR-0007 spike/validation ladder under Windows PowerShell 5.1
(the floor the installed hook command actually uses) against a live
Neovim, and fixed three real bugs the prior macOS-only session could not
catch:
- nvim-socket.ps1 / nvim-call.ps1: add `--headless`. Without it,
`nvim --server X --remote-expr Y` starts a local TUI instead of acting
as a pure remote client — stdout came back as terminal escape codes
with no result, and the responsiveness probe exited 0 even against a
dead pipe (false positive that would accept stale pidfiles). With
--headless a dead server correctly yields exit 2.
- nvim-call.ps1: eliminate double quotes from the --remote-expr value.
PowerShell 5.1 lacks PSNativeCommandArgumentPassing and strips the
embedded quotes in luaeval("..."), so nvim parsed require(...) as
Vimscript -> E117. Switched to a single-quoted Vimscript body with Lua
long-bracket [[...]] literals (zero quote chars to mangle); equally
correct under pwsh 7.
- pre_tool/init.lua: tmpdir() fell back to "/tmp" on Windows (no $TMPDIR
there), so every diff tempfile write hit a nonexistent C:\tmp and the
preview silently failed. Added a Windows branch (TMP/TEMP, forward-
slashed); the Unix branch is left byte-identical.
- log.lua: normalise the debug-log path with vim.fs.normalize so Windows
no longer produces a mixed-separator string (...\nvim-data/code-preview.log).
No-op on Unix; same physical file on Windows.
Validated end-to-end: socket discovery, RPC round-trip, the claudecode
shim opening a real preview, and live Claude Code edits across projects.
Debug logging confirmed working identically to macOS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the in-flight handoff to reflect that the validation ladder has been run on a real Windows box: records this session's four fixes (commit 69ff052), the Rung 0-4 results (incl. Claude Code shell-executing the hook command with no .cmd trampoline needed), Windows gotchas, and the updated remaining-rollout list (note: PlenaryBustedDirectory hangs headless on Windows; bash_detect is Unix-path-only). Original inbound handoff preserved in git history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a separate windows-test job (the Unix matrix strategy doesn't fit): - Lua specs run per-file via PlenaryBustedFile (PlenaryBustedDirectory hangs headless on Windows), excluding the Unix-path-only bash_detect spec until it's ported (handoff item 3). - A PowerShell 5.1 syntax/load check of the .ps1 shims (shell: powershell is Windows PowerShell 5.1 — the floor the installed hook command uses). - nvim pinned to v0.11.2 (the version the claudecode slice was validated on). Adds workflow_dispatch, and TEMPORARILY triggers on feat/windows-46 so the Windows job can be exercised on the branch before merge (remove before merge). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PlenaryBustedFile is nargs=1 — passing {minimal_init=...} glued the opts into
the filename ("cannot open <path> {...}"), and even without opts it spawns a
child nvim that wouldn't load tests/minimal_init.lua (missing plugin rtp).
Call plenary.busted.run() in-process instead: nvim is already started with
-u tests/minimal_init.lua, and busted.run sets the process exit code via :cq,
so we detect failures from $LASTEXITCODE rather than parsing ANSI output.
Verified the invocation + exit codes on macOS (pass=0, fail=2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "redirect to existing file marks bash_modified" case needs to create a /tmp file on disk (io.open fails on Windows) and have bash_detect resolve a Windows path, which is Unix-path-only today (handoff item 3). Mark it pending on Windows so the Windows CI Lua-spec job is green on the validated surface; the new-file/rm cases use forward-slash paths and keep running. Inert on Unix (verified: macOS still runs and passes it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove HANDOFF-windows-46.md: it was a cross-machine coordination scratchpad, not product docs. Durable decisions live in CONTEXT.md and ADR-0007; the Windows gotchas live as comments in the .ps1 shims. - Revert the temporary feat/windows-46 push trigger now that the PR's pull_request trigger exercises the windows-test job. workflow_dispatch stays (useful for on-demand branch runs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-machine coordination doc; not product documentation. Durable decisions live in CONTEXT.md and ADR-0007, and the Windows gotchas are captured as comments in the .ps1 shims. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Claude Code section checked the .sh shims for an executable bit, which on Windows means wrong artifact (the .ps1 are active) and ~6 spurious "not executable, run chmod +x" warnings (no exec bit on Windows; chmod is a no-op). - Add an OS-aware check_script helper: readability-only on Windows (the hook command invokes the interpreter explicitly), readability + exec bit on Unix. - Check the per-OS shim extension (.ps1 on Windows, .sh on Unix) for the Claude Code adapter scripts and the shared nvim-socket/nvim-call shims. - Gate the Codex/Copilot adapter-script checks behind not-Windows; on Windows emit one honest "not yet supported on Windows (#46)" line instead. Unix output unchanged (verified via headless checkhealth on macOS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Closes the first slice of #46: Windows 11 support for the Claude Code backend, end-to-end. Built on the in-process Lua orchestration from #47 — the only OS-specific surface is the thin shim + discovery layer, now mirrored in PowerShell.
What's in this PR
ADR-0007(shared PowerShell shim; all agents reuse it; supersedes ADR-0006) +CONTEXT.mdterm updates (Hook entry, Socket discovery, Pidfile).%LOCALAPPDATA%pidfile base on Windows; stem-based hook-marker matching across backends;chmodgated behindhas("unix");CODEX_HOMEhonoured; per-OS healthcheck (jq vs PowerShell).bin/nvim-socket.ps1(named-pipe discovery: env var → pidfile → pipe enumeration →--remote-exprprobe) andbin/nvim-call.ps1(RPC; verbatim payload splice, forward-slashed tempfile).backends/claudecode/code-{preview,close}-diff.ps1; installer writespowershell -NoProfile -ExecutionPolicy Bypass -File <path>.ps1on Windows.windows-latestjob (per-fileplenary.busted.run, sincePlenaryBustedDirectoryhangs headless on Windows; + a PowerShell 5.1 shim syntax check).Validation
.cmdtrampoline needed.Two Windows-only bugs surfaced during validation and are fixed here:
--headlessis mandatory onnvim --server --remote-expr(else it starts a TUI / false-positive probe), and PowerShell 5.1 strips double quotes from the--remote-exprvalue (fixed via single-quoted Vimscript + Lua[[...]]literals). Both are documented in the.ps1comments.Scope & known limitations
bash_detectis Unix-path-only). The core preview path (Edit/Write/MultiEdit) works; thebash_detectspec + onebash_modifiedcase are skipped on Windows pending that work.:CodePreviewInstall{Codex,CopilotCli,OpenCode}Hookswould write a hook that can't run. Worth deciding whether to add a "not yet supported on Windows" guard before this merges (see review thread).Remaining rollout (follow-up PRs)
.ps1shims + installer wiring (+ confirm each agent's command-field invocation semantics on Windows).bash_detect.luaWindows-path awareness + portable specs.health.luaper-OS script-executability checks (still assume.sh+chmod).🤖 Generated with Claude Code