Skip to content

fix(mcp): Revert recent changes and route Claude Code/Cursor manifests through Node launcher#155

Open
joshmeads wants to merge 1 commit intoory:mainfrom
joshmeads:fix/macos-posix-spawn-enoexec
Open

fix(mcp): Revert recent changes and route Claude Code/Cursor manifests through Node launcher#155
joshmeads wants to merge 1 commit intoory:mainfrom
joshmeads:fix/macos-posix-spawn-enoexec

Conversation

@joshmeads
Copy link
Copy Markdown

@joshmeads joshmeads commented May 7, 2026

Fix MacOS MCP. Currently the MCP server won't load on MacOS. POSIX posix_spawn requires byte 0-1 == "#!" for shebang detection.

PR #145 removed the #\!/bin/sh line from scripts/run.cmd to enforce strict cmd.exe stdout cleanliness on Windows MCP hosts. That regressed macOS/Linux MCP launches with:

ENOEXEC: posix_spawn '/path/scripts/run.cmd'

A single byte 0 cannot satisfy both POSIX shebang detection and cmd.exe echo suppression (which requires "@" or ":" prefix). The two constraints are mutually exclusive for a polyglot file.

Resolution:

  • Add scripts/run.cjs as a small Node dispatcher that invokes run.sh on Unix and cmd /c run.bat on Windows. Node is guaranteed in PATH for Claude Code, Cursor, OpenCode, and Codex environments. The launcher forwards SIGTERM/SIGINT/SIGHUP from the MCP host to the child so the lumen binary does not linger as an orphan process when Claude Code or Cursor shuts the launcher down.
  • Update .claude-plugin/plugin.json and .cursor/mcp.json to invoke node scripts/run.cjs stdio. Both Claude Code and Cursor now bypass run.cmd entirely.
  • Restore #\!/bin/sh to scripts/run.cmd line 1. This re-enables direct posix_spawn invocation on macOS/Linux for backward compatibility with stale plugin manifests, hook commands (hooks/hooks.json, hooks/hooks-cursor.json), and the OpenCode plugin loader.
  • Restore the shebang-tolerant Test 3 in test_run_cmd_windows.bat (matches the version at b66a339 prior to PR fix(scripts): eliminate stdout pollution in run.cmd polyglot #145). PR fix(scripts): eliminate stdout pollution in run.cmd polyglot #145 strengthened Test 3 to enforce empty stderr and added Test 4 to enforce empty stdout under cmd /c — both relied on run.cmd being the MCP entry point. Since Claude Code and Cursor now route through run.cjs, those invariants no longer apply; reverting to the original tolerant Test 3 keeps the delegation tests intact while accepting the harmless shebang noise that cmd.exe emits.

Codex already documents per-OS scripts (run.sh on Unix, run.cmd on Windows) and never relied on the polyglot being the single MCP entry point.

Compatibility:

  • Claude Code (any OS, post-update): node dispatch path
  • Cursor (any OS, post-update): node dispatch path
  • OpenCode (any OS): existing lumen.js dispatcher; unaffected
  • Codex (any OS): unchanged; per-OS install docs continue to work
  • Hooks on macOS/Linux: works via restored shebang
  • Stale Claude/Cursor manifests pointing at run.cmd on macOS: now works

Constraint: Cannot satisfy POSIX shebang and cmd.exe stdout cleanliness at byte 0 of a single file
Constraint: Plugin manifest schema does not support per-OS command dispatch
Rejected: shebang-only restoration | leaves Claude/Cursor still fragile on direct run.cmd invocation
Rejected: /bin/sh in manifest | absent on Windows
Rejected: bundle bin/lumen directly | requires marketplace per-OS distribution
Confidence: high
Scope-risk: narrow
Directive: do not remove #\!/bin/sh from run.cmd without restoring per-OS dispatch via the manifest

Summary by CodeRabbit

  • Documentation

    • Updated installation guide with launcher configuration details.
  • Chores

    • Refactored plugin launcher infrastructure for improved cross-platform support.
    • Expanded test coverage for launcher functionality.

POSIX posix_spawn requires byte 0-1 == "#\!" for shebang detection. PR ory#145
removed the "#\!/bin/sh" line from scripts/run.cmd to enforce strict cmd.exe
stdout cleanliness on Windows MCP hosts. That regressed macOS/Linux MCP
launches with:

    ENOEXEC: posix_spawn '/path/scripts/run.cmd'

A single byte 0 cannot satisfy both POSIX shebang detection and cmd.exe
echo suppression (which requires "@" or ":" prefix). The two constraints
are mutually exclusive for a polyglot file.

Resolution:

- Add scripts/run.cjs as a small Node dispatcher that invokes run.sh on
  Unix and "cmd /c run.bat" on Windows. Node is guaranteed in PATH for
  Claude Code, Cursor, OpenCode, and Codex environments. The launcher
  forwards SIGTERM/SIGINT/SIGHUP from the MCP host to the child so the
  lumen binary does not linger as an orphan process when Claude Code or
  Cursor shuts the launcher down.
- Update .claude-plugin/plugin.json and .cursor/mcp.json to invoke
  "node scripts/run.cjs stdio". Both Claude Code and Cursor now bypass
  run.cmd entirely.
- Restore "#\!/bin/sh" to scripts/run.cmd line 1. This re-enables direct
  posix_spawn invocation on macOS/Linux for backward compatibility with
  stale plugin manifests, hook commands (hooks/hooks.json,
  hooks/hooks-cursor.json), and the OpenCode plugin loader.
- Restore the shebang-tolerant Test 3 in test_run_cmd_windows.bat (matches
  the version at b66a339 prior to PR ory#145). PR ory#145 strengthened Test 3
  to enforce empty stderr and added Test 4 to enforce empty stdout under
  "cmd /c" — both relied on run.cmd being the MCP entry point. Since
  Claude Code and Cursor now route through run.cjs, those invariants no
  longer apply; reverting to the original tolerant Test 3 keeps the
  delegation tests intact while accepting the harmless shebang noise that
  cmd.exe emits.

Codex install docs are unchanged: Codex already documents per-OS scripts
(run.sh on Unix, run.cmd on Windows) and never relied on the polyglot
being the single MCP entry point.

Compatibility:

- Claude Code (any OS, post-update): node dispatch path
- Cursor (any OS, post-update): node dispatch path
- OpenCode (any OS): existing lumen.js dispatcher; unaffected
- Codex (any OS): unchanged; per-OS install docs continue to work
- Hooks on macOS/Linux: works via restored shebang
- Stale Claude/Cursor manifests pointing at run.cmd on macOS: now works

Constraint: Cannot satisfy POSIX shebang and cmd.exe stdout cleanliness at byte 0 of a single file
Constraint: Plugin manifest schema does not support per-OS command dispatch
Rejected: shebang-only restoration | leaves Claude/Cursor still fragile on direct run.cmd invocation
Rejected: /bin/sh in manifest | absent on Windows
Rejected: bundle bin/lumen directly | requires marketplace per-OS distribution
Confidence: high
Scope-risk: narrow
Directive: do not remove #\!/bin/sh from run.cmd without restoring per-OS dispatch via the manifest
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors the MCP launcher infrastructure from a batch-based polyglot runner to a Node.js entry point (scripts/run.cjs) that spawns platform-specific shell scripts. Plugin manifests are updated to invoke via node, the legacy run.cmd is converted to a POSIX shell wrapper, and test coverage is adjusted to verify the absence of known stderr pollution regressions.

Changes

MCP Launcher Refactoring

Layer / File(s) Summary
Node.js MCP Launcher
scripts/run.cjs
New Node launcher determines plugin root from environment variables, detects OS, spawns appropriate shell entrypoint (cmd.exe /c scripts/run.bat on Windows, scripts/run.sh otherwise), inherits stdio, forwards SIGTERM, SIGINT, SIGHUP to child, logs spawn errors, and propagates exit codes or re-sends termination signals to parent.
Legacy Shell Wrapper
scripts/run.cmd
Converted from Windows batch with goto :batch logic to a POSIX shell script with shebang; now execs run.sh from the same directory and forwards all arguments.
Plugin Manifest Configuration
.claude-plugin/plugin.json, .cursor/mcp.json
Both manifests updated to invoke MCP server via node command with scripts/run.cjs path and stdio transport argument, replacing prior run.cmd-based command.
Documentation & Windows Test Coverage
.cursor-plugin/INSTALL.md, scripts/test_run_cmd_windows.bat
Documentation clarifies scripts/run.cjs as the Node launcher and scripts/run.cmd as a polyglot backward-compatibility fallback. Windows test updated to verify stderr does not contain -S error token instead of requiring completely clean stderr.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • ory/lumen#145: Both PRs modify scripts/run.cmd; this PR converts batch polyglot to POSIX shell, while #145 addresses stdout pollution in the polyglot design.

Suggested reviewers

  • aeneasr
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: routing Claude Code/Cursor manifests through a Node launcher and reverting changes that caused a regression.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/run.cmd (1)

1-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use CRLF line endings for scripts/run.cmd.

Windows batch label/goto parsing can fail with LF-only line endings, particularly when labels cross 512-byte boundaries—a known cmd.exe bug documented by the bazel-lib project and others. This file is currently 190 bytes and below the boundary threshold, but converting to CRLF follows the safe practice for all .bat and .cmd files. No .gitattributes rules currently enforce line endings for these scripts, so an explicit conversion is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.cmd` around lines 1 - 13, The script uses LF-only line endings
which can break Windows cmd.exe label/goto parsing; convert scripts/run.cmd to
use CRLF line endings (Windows-style) so labels like ":batch" and the batch
section starting with "@echo off" are correctly parsed on Windows; ensure the
file is saved with CRLF encoding (e.g., via your editor or a git
core.eol/.gitattributes rule) and verify the resulting run.cmd contains CRLF
line endings throughout while preserving the existing content such as the
shebang line and the exec/call blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@scripts/run.cmd`:
- Around line 1-13: The script uses LF-only line endings which can break Windows
cmd.exe label/goto parsing; convert scripts/run.cmd to use CRLF line endings
(Windows-style) so labels like ":batch" and the batch section starting with
"@echo off" are correctly parsed on Windows; ensure the file is saved with CRLF
encoding (e.g., via your editor or a git core.eol/.gitattributes rule) and
verify the resulting run.cmd contains CRLF line endings throughout while
preserving the existing content such as the shebang line and the exec/call
blocks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e5095c8b-4e3e-445f-b7a7-5bb4b9dc6578

📥 Commits

Reviewing files that changed from the base of the PR and between 2c1f63b and a3b4745.

📒 Files selected for processing (6)
  • .claude-plugin/plugin.json
  • .cursor-plugin/INSTALL.md
  • .cursor/mcp.json
  • scripts/run.cjs
  • scripts/run.cmd
  • scripts/test_run_cmd_windows.bat

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 7, 2026

CLA assistant check
All committers have signed the CLA.

@joshmeads
Copy link
Copy Markdown
Author

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

scripts/run.cmd (1)> 1-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use CRLF line endings for scripts/run.cmd.
Windows batch label/goto parsing can fail with LF-only line endings, particularly when labels cross 512-byte boundaries—a known cmd.exe bug documented by the bazel-lib project and others. This file is currently 190 bytes and below the boundary threshold, but converting to CRLF follows the safe practice for all .bat and .cmd files. No .gitattributes rules currently enforce line endings for these scripts, so an explicit conversion is needed.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.cmd` around lines 1 - 13, The script uses LF-only line endings
which can break Windows cmd.exe label/goto parsing; convert scripts/run.cmd to
use CRLF line endings (Windows-style) so labels like ":batch" and the batch
section starting with "@echo off" are correctly parsed on Windows; ensure the
file is saved with CRLF encoding (e.g., via your editor or a git
core.eol/.gitattributes rule) and verify the resulting run.cmd contains CRLF
line endings throughout while preserving the existing content such as the
shebang line and the exec/call blocks.

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@scripts/run.cmd`:
- Around line 1-13: The script uses LF-only line endings which can break Windows
cmd.exe label/goto parsing; convert scripts/run.cmd to use CRLF line endings
(Windows-style) so labels like ":batch" and the batch section starting with
"@echo off" are correctly parsed on Windows; ensure the file is saved with CRLF
encoding (e.g., via your editor or a git core.eol/.gitattributes rule) and
verify the resulting run.cmd contains CRLF line endings throughout while
preserving the existing content such as the shebang line and the exec/call
blocks.

ℹ️ Review info

Declining. CRLF would re-break macOS posix_spawn dispatch — exactly the
regression this PR fixes.

  • run.cmd has been LF since before PR fix(scripts): eliminate stdout pollution in run.cmd polyglot #145 (the file at commit 2c1f63b
    is LF). This PR does not change line endings.
  • The file is 190 bytes — well below the 512-byte cmd.exe label/goto
    boundary bug threshold cited in the linked bazel-lib report.
  • run.bat is and remains CRLF (Windows-native). run.cmd is the
    cross-platform polyglot dispatcher and is intentionally LF so POSIX
    shebang dispatch works.

@joshmeads
Copy link
Copy Markdown
Author

joshmeads commented May 7, 2026

PR #154 by @hypn4 also targets this regression. Unfortunately I didn't see it until after I finished mine.

This PR is narrower — a minimum-diff alternative for the case where a fast-merge surgical patch is preferable to the larger architectural change.

Scope comparison

Concern This PR #154
Files changed 6 14+
Launcher scripts/run.cjs, ~50 lines CJS launcher.mjs at root, 345 lines ESM
Manifests updated Claude Code, Cursor Claude Code, Cursor, Codex, OpenCode
Hooks updated No (works via restored shebang) Yes (re-routed through launcher)
OpenCode lumen.js Untouched Updated
run.cmd polyglot Preserved + shebang restored Removed entirely
Lazy binary fetch Inherits from existing run.sh Reimplemented in Node with SHA-256 + lockfile
Node version floor Any modern Node (CJS, node: prefix → 14.18+) Node 22+ per INSTALL.md
New test coverage Reverts Test 3 to b66a339 Adds Go integration tests

Recommendation

Accept this one as the quick fix and then ask @hypn4 to split #154 up into a few PRs. SHA-256 verification, Lock file for concurrent prefetch, Cache dir resolution chain are all useful contributions but should be their own PRs.

Then look into whether the single .mjs file approach that covers all platforms from #154 works best going forward and migrate towards that idea.

However, right now MacOS is in a broken state which is why I'm suggesting minimal changes to begin with. Fully recognizing @hypn4's effort and contributions, their regression test in #153 should be added either way as a next step if this gets approved.

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.

2 participants