Skip to content

fix(ci): restore Windows MCP server support and add cross-platform script tests#121

Merged
aeneasr merged 1 commit into
mainfrom
fix/windows-mcp-114
Apr 9, 2026
Merged

fix(ci): restore Windows MCP server support and add cross-platform script tests#121
aeneasr merged 1 commit into
mainfrom
fix/windows-mcp-114

Conversation

@aeneasr
Copy link
Copy Markdown
Member

@aeneasr aeneasr commented Apr 9, 2026

Fixes #114.

Summary

  • Fixes the polyglot shebang in scripts/run.cmd — the old env -S pattern caused an error on some Unix platforms, preventing the batch (Windows) code path from being reached cleanly. The new pattern uses a plain #!/bin/sh shebang with the batch redirect comment on the next line.
  • Adds scripts/test_run_cmd_windows.bat — a Windows batch test that verifies run.cmd correctly delegates to run.bat with the right arguments and produces no unexpected stderr.
  • Adds a dedicated scripts CI job that runs on both ubuntu-latest and windows-latest, covering test_run.sh, test_run_cmd.sh (Unix), and test_run_cmd_windows.bat (Windows).

Test plan

  • bash scripts/test_run.sh — all 34 tests pass
  • bash scripts/test_run_cmd.sh — Unix delegation test passes
  • Windows script tests covered by new CI job (scripts matrix job on windows-latest)

🤖 Generated with Claude Code

…ript tests

Fix the polyglot shebang in scripts/run.cmd that broke Windows delegation
(the -S flag in `env -S` is not supported on all platforms and caused the
batch path to never be reached). Add a Windows batch test script and a
dedicated CI job that runs script tests on both ubuntu-latest and
windows-latest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aeneasr aeneasr merged commit b66a339 into main Apr 9, 2026
8 checks passed
hypn4 added a commit to hypn4/lumen that referenced this pull request May 3, 2026
cmd.exe echoed the polyglot prologue (`#!/bin/sh` and the
redirect-comment line) to STDOUT before `@echo off` was set,
prepending ~92 bytes of prompt-prefixed text to lumen's output.
This broke MCP stdio JSON-RPC framing on Claude Code, Cursor,
and other hosts that spawn run.cmd via .cmd file extension
association: the parser saw `\nC:\path>#!/bin/sh\n...` as the
first bytes and failed the handshake with "Failed to connect".

PR ory#121 only verified stderr cleanliness, and the existing
`for /f` test in test_run_cmd_windows.bat captured only the
last stdout line, so the prompt-echo prefix was masked.

Replace the prologue with a label-style line (`:<<"::CMDLITERAL"`)
which cmd.exe does not echo even with `@echo on`, while sh treats
it as a no-op `:` followed by a quoted heredoc that consumes the
cmd-only block. The shebang and `exec run.sh` move to lines 5-6
where they are reached only by sh; on Linux, execvp falls back
to /bin/sh on ENOEXEC, so direct execve of the file still routes
to sh.

Add Test 4 to test_run_cmd_windows.bat that spawns run.cmd via
`cmd /c` (matching how MCP hosts launch it) and asserts the
first stdout line is the run.bat output. Tighten Test 3 to
require empty stderr instead of only checking absence of the
historical `-S` error.
hypn4 added a commit to hypn4/lumen that referenced this pull request May 3, 2026
cmd.exe echoed the polyglot's first two lines (`#!/bin/sh` and
`# 2>NUL & @goto batch`) to stdout before `@echo off` took effect,
breaking MCP stdio JSON-RPC framing for hosts that spawn run.cmd
via .cmd extension association (Claude Code, Cursor).

Replace the prologue with `:<<"::CMDLITERAL"`: cmd.exe treats it
as a non-echoing label, while sh treats it as a quoted heredoc
that consumes the cmd-only block.

Add a regression test to test_run_cmd_windows.bat that spawns
run.cmd via `cmd /c` and asserts stdout is exactly the run.bat
output — the existing `for /f` test only kept the last stdout
line, masking any prefix pollution.

Refs: ory#121
aeneasr pushed a commit that referenced this pull request May 5, 2026
cmd.exe echoed the polyglot's first two lines (`#!/bin/sh` and
`# 2>NUL & @goto batch`) to stdout before `@echo off` took effect,
breaking MCP stdio JSON-RPC framing for hosts that spawn run.cmd
via .cmd extension association (Claude Code, Cursor).

Replace the prologue with `:<<"::CMDLITERAL"`: cmd.exe treats it
as a non-echoing label, while sh treats it as a quoted heredoc
that consumes the cmd-only block.

Add a regression test to test_run_cmd_windows.bat that spawns
run.cmd via `cmd /c` and asserts stdout is exactly the run.bat
output — the existing `for /f` test only kept the last stdout
line, masking any prefix pollution.

Refs: #121
aeneasr added a commit that referenced this pull request May 10, 2026
…t workarounds

The previous commit (01dcd96) made the Windows launcher e2e test pass by
adding /Q to the cmd.exe invocation and a lastJSONObjectLine extraction
helper that scanned for the last {...} line. Both masked a real production
bug instead of fixing it: Claude Code does not invoke run.cmd with /Q, and
its hook reader parses stdout strictly as JSON.

The actual bug: cmd.exe echoes the polyglot's first two lines (`#!/bin/sh`
and `# 2>/dev/null & @goto batch`) to stdout before `@echo off` fires inside the
:batch label, prepending ~92 bytes of prompt-prefixed text to the binary's
JSON output. Re-apply PR #145's `:<<"::CMDLITERAL"` heredoc/label trick:
cmd.exe treats line 1 as a non-echoing label, sh sees `:` opening a quoted
heredoc terminated by ::CMDLITERAL.

PR #145 was reverted because byte 0 is now `:` not `#!`, so direct
execve (Go's exec.Cmd, naive C programs) fails with ENOEXEC. But Claude
Code, Cursor, OpenCode, and other Node.js MCP hosts spawn via libuv,
which uses execvp — and execvp falls back to /bin/sh on ENOEXEC. Bash,
dash, sh, zsh, ksh all handle ENOEXEC the same way when the file is
invoked by name. So this polyglot is correct for every real production
spawn path; only Go's bare exec.Cmd is the outlier.

Strip the test workarounds:
- Drop /Q from cmd.exe; the polyglot must produce clean stdout under a
  fresh @echo on cmd.exe (matches MCP host launch).
- Delete lastJSONObjectLine; parse stdout strictly via json.Unmarshal,
  the same way Claude Code's hook reader does.
- Invoke via /bin/sh on POSIX in the Go test (matches libuv execvp's
  ENOEXEC fallback). This is not a workaround — it matches Node.js
  spawn semantics.
- Keep mustMkdirTempWithRetryCleanup; Windows briefly holding an
  exclusive handle on a freshly-exec'd .exe is a documented OS quirk,
  not a bug we cause.

Broaden POSIX shell coverage in test_run_cmd.sh: now exercises bash,
dash, sh, zsh, ksh, and direct invocation, so any cross-platform polyglot
regression has a specific named test that fails — instead of a vague
"broke linux and mac" symptom.

Restore PR #145's stricter Windows batch tests: Test 3 asserts zero
stderr bytes (cmd.exe must not surface command-not-found errors); Test 4
spawns via `cmd /c` (matching MCP host launch) and asserts stdout is
exactly the run.bat output (no prompt-echo pollution).

Refs: #121, #145

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

Unable to enable MCP Server on claude code on Windows

1 participant