fix(cursor): launch cursor-agent on Windows through its bundled node.exe - #29
Conversation
…exe (#27) resolveBin() probed with `which`, which Windows does not have, so /cursor:setup and every command failed with 'cursor-agent not found on PATH'. Finding the binary would not have been enough: the official installer ships no .exe, only cursor-agent.cmd/.ps1 shims, and since the CVE-2024-27980 fix Node refuses to spawn .cmd files without a shell. On Windows the launcher is now resolved the way cursor-agent.ps1 does it: the newest %LOCALAPPDATA%\cursor-agent\versions\<version>\node.exe running index.js. The shim is located with where.exe (absolute path, PATH-only pattern, so a repository cannot plant a binary in the working directory), with the default install dir as a fallback for a PATH that predates the install. CURSOR_AGENT_BIN may point at the shim. resolveBin() now returns { command, args }; runAgent() prepends the launcher args at every call site. macOS and Linux behaviour is unchanged. A windows-latest CI job runs the new launch tests against a real node.exe in the installer layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
freema
left a comment
There was a problem hiding this comment.
CodeForge Review
Verdict: approve | Score: 9/10
Solid, well-tested fix for launching cursor-agent on Windows. resolveBin() now returns {command, args} to route through node.exe + index.js (avoiding the .cmd/.ps1 spawn EINVAL), all call sites and the setup doctor output were updated consistently, docs were updated, and a new Windows CI job exercises the real launch path end-to-end. Verified locally: full vitest suite (minus one pre-existing, unrelated flaky process-group test), tsc, and eslint all pass.
Reviewed by CodeForge
| const names = entries.filter((e) => e.isDirectory()).map((e) => e.name); | ||
| for (const name of sortVersionDirs(names)) dirs.push(join(root, 'versions', name)); | ||
| } catch { | ||
| // No versions directory — only a node.exe beside the shim can work. |
There was a problem hiding this comment.
[MINOR] resolveWindowsInstall() swallows every readdirSync error (not just ENOENT for a missing versions directory) with a bare catch {}, so a permissions error or other unexpected filesystem failure is silently treated the same as "no versions directory" and only the shim-adjacent node.exe is tried.
Suggestion: Narrow the catch to
err.code === 'ENOENT'and rethrow (or log) other errors so unexpected failures aren't masked.
| /** | ||
| * Since Node's CVE-2024-27980 fix, spawn() rejects `.cmd`/`.bat` files unless | ||
| * it goes through a shell (EINVAL), so a shim path is translated into the | ||
| * install it launches. An `.exe` is spawnable as is; anything else is not a |
There was a problem hiding this comment.
[SUGGESTION] findOnWindows() hardcodes where.exe's location as ${SystemRoot}\System32\where.exe; on an unusual environment where SystemRoot is unset and not C:\Windows (rare but possible in some containerized Windows setups), the lookup would fail before ever reaching the LOCALAPPDATA fallback.
Suggestion: Not worth changing for this fix — just noting as a low-probability edge case; the LOCALAPPDATA fallback still covers most real installs.
|
Tested on Windows 11 (x64), Node v24.13.0, cursor-agent from the native installer. Ran
|
Fixes #27. Thanks @vilnis for the precise two-layer diagnosis — both layers check out.
What was wrong
resolveBin()probed withwhich, which Windows doesn't have, so it always threwcursor-agent not found on PATH.cursor.com/install?win32=true) ships no.exe:agent-cli-package.zipputscursor-agent.cmd/cursor-agent.ps1on PATH, and the shim runsversions\<version>\node.exe index.js. Since Node's CVE-2024-27980 fix,spawn()rejects.cmdfiles without a shell (EINVAL), so pointingCURSOR_AGENT_BINat the shim didn't help either.Verified against the current package (
2026.09.15-d2fe57e): I read itscursor-agent.cmdandcursor-agent.ps1. The.ps1accepts theYYYY.MM.DD-commitandYYYY.MM.DD-HH-MM-SS-commitdirectory names and picks the newest by date.Fix
resolveBin()returns{ command, args }instead of a string. On macOS and Linux that's{ command: <which result>, args: [] }, so behaviour there is unchanged.win32the launcher resolves the waycursor-agent.ps1does: anode.exenext to the shim, otherwise the newestversions\<version>\holding bothnode.exeandindex.js. A half-extracted newest version is skipped instead of being fatal. Same-day builds are ordered by build time, which the.ps1doesn't do.%SystemRoot%\System32\where.exe $PATH:cursor-agent(thenagent). Bothwhereandspawn()search the current directory before PATH on Windows, so the absolute path plus the$PATH:pattern stops a repository from planting its ownwhere.exe/cursor-agent.exe.%LOCALAPPDATA%\cursor-agent, for a Claude Code session whose PATH predates the install. The installer only updates PATH for new shells.CURSOR_AGENT_BINmay point at the.cmd/.ps1shim.runAgent(args, opts)prepends the launcher args. It replaces theresolveBin()+run(bin, …)pairs inauthStatus,listModels(×2),listConfiguredMcps,listSessionsand the--versioncheck insetup.mjs;runHeadlessspreads them into itsspawn.setupdisplays the resolved launcher viadescribeBin().The shim's
CURSOR_INVOKED_ASisn't replicated on purpose. In the currentindex.jsit only gates a one-time TTY tip ("You can start the Cursor CLI withagent"), which headless runs never show.Tests
tests/windows-bin.test.mjs: version-dir ordering, install resolution (newest, incomplete version skipped, node.exe beside the shim, nothing installed) and shim/.exepath translation. These run on every platform.node.execopied into the installer layout. It checks that spawning the.cmdshim fails withEINVAL(the original bug), thatresolveBin()finds the shim viawhere.exeand launches it, the%LOCALAPPDATA%fallback, andCURSOR_AGENT_BINpointing at the shim.windows-binjob onwindows-latestruns just that file. The rest of the suite assumes POSIX paths (the stub binary is a shebang.mjs), so putting Windows into the main matrix is a separate piece of work.Test plan
npm test: 180 passed, 4 skipped (the Windows-only block on macOS)npm run lint,npm run typechecknode scripts/setup.mjs -- --doctoron macOS against a realcursor-agent: binary, version and auth all ✓windows-binCI job green/cursor:setupon Windows 11 with the native installer🤖 Generated with Claude Code