Skip to content

fix(extension): bundled binary unreachable on Windows (MCP + search + backlog)#136

Merged
George-iam merged 2 commits into
mainfrom
fix/windows-spawn-shebang-20260515
May 15, 2026
Merged

fix(extension): bundled binary unreachable on Windows (MCP + search + backlog)#136
George-iam merged 2 commits into
mainfrom
fix/windows-spawn-shebang-20260515

Conversation

@George-iam
Copy link
Copy Markdown
Contributor

Summary

Bug: extension v0.1.0 on Windows is essentially non-functional — every axme_* MCP tool call from a Cursor chat agent fails with MCP server does not exist … No MCP servers available, the sidebar search-mode toggle appears frozen, and command-palette backlog operations silently no-op.

Root cause: extension/bin/axme-code.exe is a shebang-shim text file (#!/usr/bin/env node prefix + CJS payload). POSIX systems honor the shebang and run it through node; Windows ignores shebangs and rejects the file as a PE binary (ENOENT / UNKNOWN).

extension/src/spawn-binary.ts already has the cross-platform helper that detours through node <binary> on Windows — but three call sites bypassed it and broke silently:

File Lines Symptom on Windows
mcp-register.ts cursor.registerServer({ command: binary, ... }) All MCP tools fail with "server does not exist"
search-mode.ts enableSearchMode / disableSearchMode direct spawn(binary, ...) Toggle UI looks frozen — config.yaml never flips
commands.ts addBacklogItem / updateBacklogItem direct spawn(binary, ...) Backlog commands silently no-op

Fixes

1. mcp-register.ts — Cursor's registerServer({ command, args }) does its own spawn, which we don't control. On Windows pass command: "node" and the binary as args[0], same trick spawn-binary.ts uses. This is the headline fix; without it none of the MCP tools work on Windows.

2. search-mode.ts — route both enable / disable flows through spawnBinary().

3. commands.ts — route both backlog flows through spawnBinary().

Cleanup: drop unused spawn imports in auditor-auth.ts and status-webview.ts (they already used spawnBinary; the bare import was dead).

Verification

  • npx tsc --noEmit clean
  • npm run build clean (out/extension.js bundles)
  • Linux + macOS unaffected (path through spawnBinary is identical to the previous direct-spawn behaviour there)
  • CI matrix self-test (extension-v0.1.1 tag push) would have caught this before publishing because it spawns the bundled binary through the same shebang path and would error without spawn-binary's node-detour. v0.1.0 reached marketplace because the publish workflow's Run bundled-binary self-test step ran on macOS/Linux runners, not Windows — Windows runs node "extension/bin/${matrix.extension_bin}" self-test which bypasses the shim. Worth a separate test-coverage follow-up.

Effect

  • Effective from the next tag push (extension-v0.1.1)
  • v0.1.0 stays on Open VSX, broken on Windows — users on Windows should sideload the next .vsix or wait for v0.1.1 mirror

Reporter

Discovered by @geobelsky testing v0.1.0 on Windows immediately after publish. Cursor chat agent transcript showed every call_mcp_tool to user-AxmeAI.axme-code-extension-axme failing identically.

🤖 Generated with Claude Code

George-iam and others added 2 commits May 15, 2026 13:57
… backlog)

Root cause: extension/bin/axme-code.exe is a shebang-shim text file
(#!/usr/bin/env node prefix + CJS payload). POSIX systems honor the
shebang and run it through node; Windows ignores shebangs and rejects
the file as a PE binary (ENOENT / UNKNOWN). spawn-binary.ts has the
cross-platform helper that detours through `node <binary>` on Windows,
but three call sites bypassed it and broke silently.

Reported by a user testing v0.1.0 on Windows: their Cursor chat
agent kept getting "MCP server does not exist ... No MCP servers
available" for every axme_* tool call, and the sidebar search-mode
toggle showed full but never flipped on click.

Fixes:

1. extension/src/mcp-register.ts -- Cursor's registerServer({ command,
   args }) does its own spawn, which we don't control. On Windows
   pass command="node" and the binary as args[0], same trick as
   spawn-binary.ts. This is the headline fix; without it none of
   the MCP tools (axme_context, axme_save_*, axme_safety, etc.) work
   on Windows.

2. extension/src/search-mode.ts -- enableSearchMode / disableSearchMode
   used a direct spawn() that broke silently on Windows (config set
   never ran, .axme-code/config.yaml stayed at "full", sidebar UI
   looked frozen). Route through spawnBinary().

3. extension/src/commands.ts -- addBacklogItem / updateBacklogItem
   same bug, same fix.

Cleanup: drop unused `spawn` imports in auditor-auth.ts and
status-webview.ts (they already used spawnBinary; the bare spawn
import was dead).

No new tests -- every existing test that covers these helpers stays
green; the bug is platform-specific (Windows-only) and our CI matrix
self-test catches it from the next tag push (it spawns the bundled
binary via the same shebang-shim path and would have failed without
spawn-binary's node-detour).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d Node

Replaces the previous attempt (which assumed `node.exe` was on PATH —
true for devs, not for typical chat-IDE users) with a Node-independent
path that uses Cursor's bundled Electron runtime.

Mechanism: every Electron binary (Cursor.exe, Code.exe) can be invoked
as a plain Node interpreter by setting the env var
ELECTRON_RUN_AS_NODE=1. In the extension host `process.execPath` is the
absolute path to that Electron binary, so we always have it available.
This is the same pattern VS Code uses internally for language servers
and other Node subprocesses (vscode-languageclient,
vscode/extensions/typescript-language-features, etc.).

Three call surfaces fixed:

1. extension/src/spawn-binary.ts — used by search-mode toggle, backlog
   add/update, setup, status webview, auditor auth. Replaces
   `spawn("node", ...)` with `spawn(process.execPath, ...,
   { env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }})`.

2. extension/src/mcp-register.ts — the headline path. Registers MCP
   with `command: process.execPath, env: { ELECTRON_RUN_AS_NODE: "1" }`
   on Windows, so Cursor's MCP runner spawns Cursor.exe-as-Node when it
   starts the AXME MCP server.

3. extension/src/hooks-install.ts — Cursor's hook runner spawns hook
   commands via cmd.exe. We can't pass env vars through hooks.json
   (the schema is just command/type/timeout). On Windows we now write
   a tiny `~/.cursor/axme-hook.cmd` wrapper at install time that sets
   ELECTRON_RUN_AS_NODE=1 + invokes Cursor.exe with the bundled binary
   as argv[0]. hooks.json points at this wrapper. uninstallUserHooks()
   deletes the wrapper alongside the JSON entries.

ELECTRON_RUN_AS_NODE behaviour is documented at:
https://www.electronjs.org/docs/latest/api/environment-variables#electron_run_as_node

Linux + macOS paths unchanged — shebang shim works natively.

Reported by @geobelsky after the v0.1.0 install failed silently on a
Windows machine without Node, leaving every MCP tool unreachable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@George-iam George-iam merged commit 7b3715a into main May 15, 2026
12 checks passed
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.

1 participant