fix(agent/cursor): route Windows launcher through PowerShell -File to… - #1709
Conversation
|
@HuChundong is attempting to deploy a commit to the IndexLabs Team on Vercel. A member of the Team first needs to authorize it. |
|
Hi @HuChundong, thanks for tracking down the root cause here — the analysis of the One thing before this can merge: the PR is currently The rest of the diff ( Also worth noting: the issue this PR closes (#1297) was closed prematurely by the reporter — the actual bug is still real, and another user (@brholtkamp) confirmed it on 2026-04-22. Suggest reopening #1297 so the GitHub auto-close on merge lands meaningfully and so users still hitting it have a single thread to subscribe to. |
9ae686f to
8ba9bf0
Compare
|
fixed, hope we can merge this. ^_^ |
|
Hi @HuChundong, thanks for the quick turnaround on the rebase + dropping Unfortunately the rebase appears to have squashed against an outdated base, and the PR now silently reverts three unrelated commits already on
Could you re-rebase against the latest git fetch origin
git reset --hard origin/main
git cherry-pick 8ba9bf0c
# resolve any trivial conflicts (there shouldn't be any in cursor*.go)
git push --force-with-leaseAfter that, |
8ba9bf0 to
539823b
Compare
|
should be ok now |
|
@HuChundong sorry to keep going back and forth on this — the rebase actually went the other way this time.
The cursor change itself is identical to last push and still looks good — the issue is purely the base your branch is on. I think the cleanest reset is to drop the local branch entirely and re-create it from git fetch origin
git checkout fix/cursor-windows-long-prompt
git reset --hard origin/main # important: origin/main, not local main
git cherry-pick 539823b6 # your current cursor commit
git diff origin/main..HEAD --stat # should list ONLY the 6 server/pkg/agent/cursor*.go files
git push --force-with-leaseIf |
539823b to
17d372a
Compare
|
maybe i should use multica to create this PR,😂 |
|
@HuChundong much closer this time —
These were added by #1761 ( I think the issue is that your local git fetch origin
git checkout fix/cursor-windows-long-prompt
git reset --hard origin/main # <- origin/main, NOT main
git cherry-pick 17d372ad # your current cursor commit
git diff origin/main..HEAD --stat # MUST show only 6 files, all under server/pkg/agent/cursor*
git push --force-with-leaseThe check after the cherry-pick is the important one — if |
… preserve multi-line prompts On Windows the official cursor-agent installer ships cursor-agent.cmd whose body is `powershell ... -File cursor-agent.ps1 %*`. CreateProcess for a .cmd file goes through cmd.exe, and `%*` in a batch file is expanded by re-tokenising the original command line, which mangles arguments containing newlines or other whitespace - most notably a long, multi-line `-p <prompt>`. The agent then only sees a truncated prompt and fails with "Workspace Trust Required" or exits 1 immediately. When LookPath resolves cursor-agent to a .cmd/.bat launcher and a sibling cursor-agent.ps1 exists, invoke PowerShell directly with `-File <ps1>` so Go's os/exec passes each argv as a discrete token. This is exactly what the .cmd does internally; we just skip the cmd.exe re-tokenisation step. PowerShell host resolution prefers pwsh.exe (PS 7) on PATH, then powershell.exe on PATH, and finally falls back to %SystemRoot%\System32\WindowsPowerShell\v1.0. Platform-specific code is split via build tags (cursor_invocation_windows.go / cursor_invocation_other.go) so non-Windows builds carry no Windows-only dependencies. The lookup is exposed as a package variable to make the Windows path fully unit-testable without spawning real PowerShell. Five unit tests cover: passthrough on non-launcher targets, successful rewrite with a multi-line prompt, .exe direct launch (skip), missing .ps1 (skip), and missing PowerShell host (skip). The change leaves macOS / Linux behaviour entirely untouched and stays on the official cursor-agent launch chain - no node.exe direct invocation, no prompt mutation, no extra flags. Closes multica-ai#1297 Made-with: Cursor
17d372a to
3a01bc9
Compare
|
Diffstat is clean now (just the 6 cursor files), CI is green, local |
… preserve multi-line prompts (multica-ai#1709) On Windows the official cursor-agent installer ships cursor-agent.cmd whose body is `powershell ... -File cursor-agent.ps1 %*`. CreateProcess for a .cmd file goes through cmd.exe, and `%*` in a batch file is expanded by re-tokenising the original command line, which mangles arguments containing newlines or other whitespace - most notably a long, multi-line `-p <prompt>`. The agent then only sees a truncated prompt and fails with "Workspace Trust Required" or exits 1 immediately. When LookPath resolves cursor-agent to a .cmd/.bat launcher and a sibling cursor-agent.ps1 exists, invoke PowerShell directly with `-File <ps1>` so Go's os/exec passes each argv as a discrete token. This is exactly what the .cmd does internally; we just skip the cmd.exe re-tokenisation step. PowerShell host resolution prefers pwsh.exe (PS 7) on PATH, then powershell.exe on PATH, and finally falls back to %SystemRoot%\System32\WindowsPowerShell\v1.0. Platform-specific code is split via build tags (cursor_invocation_windows.go / cursor_invocation_other.go) so non-Windows builds carry no Windows-only dependencies. The lookup is exposed as a package variable to make the Windows path fully unit-testable without spawning real PowerShell. Five unit tests cover: passthrough on non-launcher targets, successful rewrite with a multi-line prompt, .exe direct launch (skip), missing .ps1 (skip), and missing PowerShell host (skip). The change leaves macOS / Linux behaviour entirely untouched and stays on the official cursor-agent launch chain - no node.exe direct invocation, no prompt mutation, no extra flags. Closes multica-ai#1297 Made-with: Cursor
… preserve multi-line prompts (multica-ai#1709) On Windows the official cursor-agent installer ships cursor-agent.cmd whose body is `powershell ... -File cursor-agent.ps1 %*`. CreateProcess for a .cmd file goes through cmd.exe, and `%*` in a batch file is expanded by re-tokenising the original command line, which mangles arguments containing newlines or other whitespace - most notably a long, multi-line `-p <prompt>`. The agent then only sees a truncated prompt and fails with "Workspace Trust Required" or exits 1 immediately. When LookPath resolves cursor-agent to a .cmd/.bat launcher and a sibling cursor-agent.ps1 exists, invoke PowerShell directly with `-File <ps1>` so Go's os/exec passes each argv as a discrete token. This is exactly what the .cmd does internally; we just skip the cmd.exe re-tokenisation step. PowerShell host resolution prefers pwsh.exe (PS 7) on PATH, then powershell.exe on PATH, and finally falls back to %SystemRoot%\System32\WindowsPowerShell\v1.0. Platform-specific code is split via build tags (cursor_invocation_windows.go / cursor_invocation_other.go) so non-Windows builds carry no Windows-only dependencies. The lookup is exposed as a package variable to make the Windows path fully unit-testable without spawning real PowerShell. Five unit tests cover: passthrough on non-launcher targets, successful rewrite with a multi-line prompt, .exe direct launch (skip), missing .ps1 (skip), and missing PowerShell host (skip). The change leaves macOS / Linux behaviour entirely untouched and stays on the official cursor-agent launch chain - no node.exe direct invocation, no prompt mutation, no extra flags. Closes multica-ai#1297 Made-with: Cursor
…e-tokenised (MUL-4992) On Windows a Cursor task whose prompt contains CLI-like flags failed in ~2s with `error: unknown option '-X'` and no agent output (#5649). A pasted build log such as go build -ldflags "-X main.version=foo" -o bin/server ./cmd/server was enough to kill the run. buildCursorArgs put the whole prompt in argv as the positional after -p. The official Windows launcher chain ends in `& node.exe index.js $args` inside cursor-agent.ps1, where PowerShell re-serialises $args onto node's command line. Under Windows PowerShell 5.1 and pwsh <= 7.2 (Legacy native argument passing) an argument holding embedded double quotes is not re-escaped, so the quoted region closes early, node's argv parser re-splits at the interior spaces, and `-X` reaches commander.js as a standalone flag. #1709 removed the cmd.exe `%*` re-tokenisation but stopped at the Go -> PowerShell boundary, one hop before this. Its Windows tests only compare the argv slice Go builds and never execute a shim, which is why the gap stayed invisible. Fix: keep the prompt off every command line. cursor-agent's -p is a boolean print-mode switch and the prompt is positional; with no positional prompt and a non-TTY stdin the CLI reads stdin to EOF and uses that as the prompt. So drop the prompt from argv on all platforms and write it to stdin, leaving only fixed, content-free flags in argv. No shell or launcher on any platform can re-tokenise what is not on a command line. The write runs in its own goroutine: a prompt larger than the pipe buffer (~64 KiB) blocks mid-write until the child drains it, and the child cannot drain while nothing reads its stdout. Closing stdin signals end-of-prompt, so it is closed on both success and error paths, and on cancellation to release a blocked write. Write failures surface in the result diagnostic, ranked below explicit agent errors so an early child exit (bad auth, bad flag) is not masked by the resulting EPIPE. Tests: a prompt carrying the exact `-ldflags "-X ..."` shape must arrive byte-for-byte on stdin and appear nowhere in argv; a 512 KiB prompt must not deadlock; the prompt is written verbatim (the CLI trims it, we do not). Both new unix tests fail against the pre-fix code. A Windows-tagged test drives a real PowerShell host through the same .cmd -> -File rewrite. Closes #5649 Co-authored-by: multica-agent <github@multica.ai>
…e-tokenised (MUL-4992) (#5711) * fix(agent/cursor): send prompt on stdin so CLI-like flags cannot be re-tokenised (MUL-4992) On Windows a Cursor task whose prompt contains CLI-like flags failed in ~2s with `error: unknown option '-X'` and no agent output (#5649). A pasted build log such as go build -ldflags "-X main.version=foo" -o bin/server ./cmd/server was enough to kill the run. buildCursorArgs put the whole prompt in argv as the positional after -p. The official Windows launcher chain ends in `& node.exe index.js $args` inside cursor-agent.ps1, where PowerShell re-serialises $args onto node's command line. Under Windows PowerShell 5.1 and pwsh <= 7.2 (Legacy native argument passing) an argument holding embedded double quotes is not re-escaped, so the quoted region closes early, node's argv parser re-splits at the interior spaces, and `-X` reaches commander.js as a standalone flag. #1709 removed the cmd.exe `%*` re-tokenisation but stopped at the Go -> PowerShell boundary, one hop before this. Its Windows tests only compare the argv slice Go builds and never execute a shim, which is why the gap stayed invisible. Fix: keep the prompt off every command line. cursor-agent's -p is a boolean print-mode switch and the prompt is positional; with no positional prompt and a non-TTY stdin the CLI reads stdin to EOF and uses that as the prompt. So drop the prompt from argv on all platforms and write it to stdin, leaving only fixed, content-free flags in argv. No shell or launcher on any platform can re-tokenise what is not on a command line. The write runs in its own goroutine: a prompt larger than the pipe buffer (~64 KiB) blocks mid-write until the child drains it, and the child cannot drain while nothing reads its stdout. Closing stdin signals end-of-prompt, so it is closed on both success and error paths, and on cancellation to release a blocked write. Write failures surface in the result diagnostic, ranked below explicit agent errors so an early child exit (bad auth, bad flag) is not masked by the resulting EPIPE. Tests: a prompt carrying the exact `-ldflags "-X ..."` shape must arrive byte-for-byte on stdin and appear nowhere in argv; a 512 KiB prompt must not deadlock; the prompt is written verbatim (the CLI trims it, we do not). Both new unix tests fail against the pre-fix code. A Windows-tagged test drives a real PowerShell host through the same .cmd -> -File rewrite. Closes #5649 Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): prove the Windows launcher fix on both PowerShell hosts in CI The stdin fix has to hold on the host that actually exhibits the bug. powershell.exe (5.1) and pwsh <= 7.2 default to Legacy native argument passing; pwsh >= 7.3 defaults to Standard. A fix verified only on the newer host would not be a fix for the reporter. Run the shim probe against every PowerShell host on PATH rather than only the one defaultPowerShellLookup would select, and hook the windows-tagged launcher tests into the existing windows-execenv CI job. These tests are windows-tagged and the backend job runs on ubuntu, so until now they ran nowhere. Co-authored-by: multica-agent <github@multica.ai> * test(ci): run Windows launcher tests verbosely so skips are visible A skipped or unmatched test still reports "ok", which would make the Windows job look like coverage it is not providing. Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): close both coverage gaps in the #5649 regression tests Two tests claimed guarantees they did not actually establish. Windows: the fake cursor-agent.ps1 called [Console]::In.ReadToEnd() and wrote the result itself, so it never launched a native child. The official shim ends in `& node.exe index.js $args`, and that last hop is precisely where the bug lives -- PowerShell re-serialises $args onto the child command line, and whether the child inherits stdin was left unproven. The fake ps1 now re-executes the test binary as a real native child (helper-process idiom), which records the argv it actually received and drains stdin. Both PowerShell hosts still run. Interlock: the large-prompt fake drained stdin before writing any stdout, so the child always unblocked the parent immediately and the test passed even against a synchronous write -- it could not fail for the reason it existed. The fake now floods stdout past pipe capacity *before* reading stdin, creating the real mutual block. Verified: with the writer made synchronous the test deadlocks to its 30s timeout, and passes only with the concurrent writer. Also adds the missing cancellation case: a child that never reads stdin leaves the writer blocked forever, so cancelling the context must close stdin, release the writer and settle the run as aborted. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
…e-tokenised (MUL-4992) (multica-ai#5711) * fix(agent/cursor): send prompt on stdin so CLI-like flags cannot be re-tokenised (MUL-4992) On Windows a Cursor task whose prompt contains CLI-like flags failed in ~2s with `error: unknown option '-X'` and no agent output (multica-ai#5649). A pasted build log such as go build -ldflags "-X main.version=foo" -o bin/server ./cmd/server was enough to kill the run. buildCursorArgs put the whole prompt in argv as the positional after -p. The official Windows launcher chain ends in `& node.exe index.js $args` inside cursor-agent.ps1, where PowerShell re-serialises $args onto node's command line. Under Windows PowerShell 5.1 and pwsh <= 7.2 (Legacy native argument passing) an argument holding embedded double quotes is not re-escaped, so the quoted region closes early, node's argv parser re-splits at the interior spaces, and `-X` reaches commander.js as a standalone flag. multica-ai#1709 removed the cmd.exe `%*` re-tokenisation but stopped at the Go -> PowerShell boundary, one hop before this. Its Windows tests only compare the argv slice Go builds and never execute a shim, which is why the gap stayed invisible. Fix: keep the prompt off every command line. cursor-agent's -p is a boolean print-mode switch and the prompt is positional; with no positional prompt and a non-TTY stdin the CLI reads stdin to EOF and uses that as the prompt. So drop the prompt from argv on all platforms and write it to stdin, leaving only fixed, content-free flags in argv. No shell or launcher on any platform can re-tokenise what is not on a command line. The write runs in its own goroutine: a prompt larger than the pipe buffer (~64 KiB) blocks mid-write until the child drains it, and the child cannot drain while nothing reads its stdout. Closing stdin signals end-of-prompt, so it is closed on both success and error paths, and on cancellation to release a blocked write. Write failures surface in the result diagnostic, ranked below explicit agent errors so an early child exit (bad auth, bad flag) is not masked by the resulting EPIPE. Tests: a prompt carrying the exact `-ldflags "-X ..."` shape must arrive byte-for-byte on stdin and appear nowhere in argv; a 512 KiB prompt must not deadlock; the prompt is written verbatim (the CLI trims it, we do not). Both new unix tests fail against the pre-fix code. A Windows-tagged test drives a real PowerShell host through the same .cmd -> -File rewrite. Closes multica-ai#5649 Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): prove the Windows launcher fix on both PowerShell hosts in CI The stdin fix has to hold on the host that actually exhibits the bug. powershell.exe (5.1) and pwsh <= 7.2 default to Legacy native argument passing; pwsh >= 7.3 defaults to Standard. A fix verified only on the newer host would not be a fix for the reporter. Run the shim probe against every PowerShell host on PATH rather than only the one defaultPowerShellLookup would select, and hook the windows-tagged launcher tests into the existing windows-execenv CI job. These tests are windows-tagged and the backend job runs on ubuntu, so until now they ran nowhere. Co-authored-by: multica-agent <github@multica.ai> * test(ci): run Windows launcher tests verbosely so skips are visible A skipped or unmatched test still reports "ok", which would make the Windows job look like coverage it is not providing. Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): close both coverage gaps in the multica-ai#5649 regression tests Two tests claimed guarantees they did not actually establish. Windows: the fake cursor-agent.ps1 called [Console]::In.ReadToEnd() and wrote the result itself, so it never launched a native child. The official shim ends in `& node.exe index.js $args`, and that last hop is precisely where the bug lives -- PowerShell re-serialises $args onto the child command line, and whether the child inherits stdin was left unproven. The fake ps1 now re-executes the test binary as a real native child (helper-process idiom), which records the argv it actually received and drains stdin. Both PowerShell hosts still run. Interlock: the large-prompt fake drained stdin before writing any stdout, so the child always unblocked the parent immediately and the test passed even against a synchronous write -- it could not fail for the reason it existed. The fake now floods stdout past pipe capacity *before* reading stdin, creating the real mutual block. Verified: with the writer made synchronous the test deadlocks to its 30s timeout, and passes only with the concurrent writer. Also adds the missing cancellation case: a child that never reads stdin leaves the writer blocked forever, so cancelling the context must close stdin, release the writer and settle the run as aborted. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
…e-tokenised (MUL-4992) (multica-ai#5711) * fix(agent/cursor): send prompt on stdin so CLI-like flags cannot be re-tokenised (MUL-4992) On Windows a Cursor task whose prompt contains CLI-like flags failed in ~2s with `error: unknown option '-X'` and no agent output (multica-ai#5649). A pasted build log such as go build -ldflags "-X main.version=foo" -o bin/server ./cmd/server was enough to kill the run. buildCursorArgs put the whole prompt in argv as the positional after -p. The official Windows launcher chain ends in `& node.exe index.js $args` inside cursor-agent.ps1, where PowerShell re-serialises $args onto node's command line. Under Windows PowerShell 5.1 and pwsh <= 7.2 (Legacy native argument passing) an argument holding embedded double quotes is not re-escaped, so the quoted region closes early, node's argv parser re-splits at the interior spaces, and `-X` reaches commander.js as a standalone flag. multica-ai#1709 removed the cmd.exe `%*` re-tokenisation but stopped at the Go -> PowerShell boundary, one hop before this. Its Windows tests only compare the argv slice Go builds and never execute a shim, which is why the gap stayed invisible. Fix: keep the prompt off every command line. cursor-agent's -p is a boolean print-mode switch and the prompt is positional; with no positional prompt and a non-TTY stdin the CLI reads stdin to EOF and uses that as the prompt. So drop the prompt from argv on all platforms and write it to stdin, leaving only fixed, content-free flags in argv. No shell or launcher on any platform can re-tokenise what is not on a command line. The write runs in its own goroutine: a prompt larger than the pipe buffer (~64 KiB) blocks mid-write until the child drains it, and the child cannot drain while nothing reads its stdout. Closing stdin signals end-of-prompt, so it is closed on both success and error paths, and on cancellation to release a blocked write. Write failures surface in the result diagnostic, ranked below explicit agent errors so an early child exit (bad auth, bad flag) is not masked by the resulting EPIPE. Tests: a prompt carrying the exact `-ldflags "-X ..."` shape must arrive byte-for-byte on stdin and appear nowhere in argv; a 512 KiB prompt must not deadlock; the prompt is written verbatim (the CLI trims it, we do not). Both new unix tests fail against the pre-fix code. A Windows-tagged test drives a real PowerShell host through the same .cmd -> -File rewrite. Closes multica-ai#5649 Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): prove the Windows launcher fix on both PowerShell hosts in CI The stdin fix has to hold on the host that actually exhibits the bug. powershell.exe (5.1) and pwsh <= 7.2 default to Legacy native argument passing; pwsh >= 7.3 defaults to Standard. A fix verified only on the newer host would not be a fix for the reporter. Run the shim probe against every PowerShell host on PATH rather than only the one defaultPowerShellLookup would select, and hook the windows-tagged launcher tests into the existing windows-execenv CI job. These tests are windows-tagged and the backend job runs on ubuntu, so until now they ran nowhere. Co-authored-by: multica-agent <github@multica.ai> * test(ci): run Windows launcher tests verbosely so skips are visible A skipped or unmatched test still reports "ok", which would make the Windows job look like coverage it is not providing. Co-authored-by: multica-agent <github@multica.ai> * test(agent/cursor): close both coverage gaps in the multica-ai#5649 regression tests Two tests claimed guarantees they did not actually establish. Windows: the fake cursor-agent.ps1 called [Console]::In.ReadToEnd() and wrote the result itself, so it never launched a native child. The official shim ends in `& node.exe index.js $args`, and that last hop is precisely where the bug lives -- PowerShell re-serialises $args onto the child command line, and whether the child inherits stdin was left unproven. The fake ps1 now re-executes the test binary as a real native child (helper-process idiom), which records the argv it actually received and drains stdin. Both PowerShell hosts still run. Interlock: the large-prompt fake drained stdin before writing any stdout, so the child always unblocked the parent immediately and the test passed even against a synchronous write -- it could not fail for the reason it existed. The fake now floods stdout past pipe capacity *before* reading stdin, creating the real mutual block. Verified: with the writer made synchronous the test deadlocks to its 30s timeout, and passes only with the concurrent writer. Also adds the missing cancellation case: a child that never reads stdin leaves the writer blocked forever, so cancelling the context must close stdin, release the writer and settle the run as aborted. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
What does this PR do?
Fixes Multica-spawned
cursor-agentalways exiting 1 on Windows when theprompt contains newlines (i.e. nearly every real task).
Root cause. The official
cursor-agentinstaller shipscursor-agent.cmd, whose body isCreateProcessfor a.cmdfile routes throughcmd.exe, and%*in abatch file is expanded by re-tokenising the original command line.
That re-tokenisation mangles any argument containing newlines or other
whitespace — most notably a long, multi-line
-p <prompt>. The agentends up seeing a truncated prompt and either reports
Workspace Trust Requiredor exits with status 1 before producing a session id.Manually pasting an equivalent command into PowerShell works because
PowerShell tokenises the user input itself instead of recovering tokens
from the command-line string — exactly what we want Go's
os/execto dofor us.
Fix. When
LookPathresolvescursor-agentto a.cmd/.batlauncher and a sibling
cursor-agent.ps1exists, the daemon now invokesPowerShell directly:
This is exactly what the
.cmddoes internally; we simply skip thecmd.exere-tokenisation step. Eachargvis passed as a discretetoken, so multi-line prompts and other whitespace-heavy values survive
intact. macOS / Linux behaviour is unchanged (the path is gated by
//go:build windows); the official cursor-agent launch chain ispreserved (no
node.exeshortcut, no prompt mutation, no extra flags).PowerShell host resolution prefers
pwsh.exe(PS 7) on PATH, thenpowershell.exeon PATH, then falls back to%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe. Thelookup is a package-level variable so it can be stubbed in tests.
Related Issue
Closes #1297
Type of Change
Changes Made
server/pkg/agent/cursor.go— callchooseCursorInvocation(...)topick
argv[0]and the full argv just beforeexec.CommandContext.server/pkg/agent/cursor_invocation.go— sharedchooseCursorInvocationentry point with a doc-comment that explainsthe cmd
%*trap, so future maintainers don't "optimise" the rewriteaway.
server/pkg/agent/cursor_invocation_other.go(//go:build !windows)— no-op passthrough, keeps non-Windows builds free of any
Windows-only dependency.
server/pkg/agent/cursor_invocation_windows.go(//go:build windows)— detect
.cmd/.bat+ siblingcursor-agent.ps1, build thePowerShell argv, log a structured
Infoline on activation. ExportspowerShellLookupas a package variable for test injection.server/pkg/agent/cursor_invocation_test.go— passthrough behaviourfor non-launcher targets (runs on every platform).
server/pkg/agent/cursor_invocation_windows_test.go(
//go:build windows) — four tests covering: successful rewrite witha multi-line prompt,
.exedirect launch (skip), missing.ps1(skip), missing PowerShell host (skip). All paths use a stubbed
powerShellLookupso the suite never spawns real PowerShell.server/pkg/agent/exec_fixture_windows_test.go— restored Windowstest helper
writeTestExecutable. Required for thepkg/agenttestbinary to compile on Windows (consumed by existing
claude_test.go/codex_test.go/kimi_test.go); without it thewhole test package can't build on Windows and the new cursor tests
can't run either.
How to Test
Unit tests (any platform):
Expected: all
TestChooseCursorInvocation_*andTestPlatformCursorInvocation_*cases pass on Windows; thepassthrough case also passes on macOS / Linux.
End-to-end on Windows (reproduces the issue from [Bug]: [Windows] Multica-spawned cursor-agent always exits 1; manual run succeeds #1297):
Stop the existing daemon.
Build & start the daemon from this branch (
make daemon/ yourusual launch).
Assign a Cursor agent task with a real, multi-line prompt
(anything ≥ a few hundred characters with newlines is enough; the
reported failure trigger is a typical Multica system prompt).
Before the fix: task fails almost immediately with
cursor-agent exited with error: exit status 1and the stderrtail mentions
Workspace Trust Required.After the fix: task runs to completion. The daemon log shows a
new line:
and the subsequent
agent commandline listsargv[0]asPowerShell with
-NoProfile -ExecutionPolicy Bypass -File ... cursor-agent.ps1followed by the original cursor-agent argv.Sanity on non-Windows: behaviour is unchanged (the new code path
is gated by
//go:build windows);go test ./pkg/agent/...shouldpass exactly as on
main.Checklist
cmd %*trap)Risks considered
-ExecutionPolicy Bypass, thesame flag the official
cursor-agent.cmdalready uses, so we don'tweaken policy beyond what the upstream installer does.
-NoProfilematches the.cmd,guaranteeing identical environment.
pwsh.exeandpowershell.exeare missing →chooseCursorInvocationfalls back tothe original
.cmdinvocation (the testTestPlatformCursorInvocation_SkipsWhenPowerShellMissingpins thisbehaviour). Worst case is the pre-existing behaviour, never worse.
cursor-agentshipped without.ps1. Same fallback —TestPlatformCursorInvocation_SkipsWhenPS1Missingpins it.when we actually see a sibling
.ps1; other layouts hit thepassthrough and behave exactly as today.
AI Disclosure
AI tool used: Cursor
Prompt / approach:
Reproduced #1297 locally with a small
go run-able harness that drivesbackend.Executewith both a short prompt and an embedded productionlong prompt, confirming the failure mode and bisecting the cause to the
.cmd%*re-tokenisation. Iterated on the fix in chat: started from aprompt-normalisation hypothesis (rejected — masks the root cause and
silently rewrites user content), then moved to invoking PowerShell
directly with
-Fileso we stay on the official launch chain whilesidestepping
cmd.exe. Refactored the platform-specific bits behind abuild-tag boundary, exposed the PowerShell lookup as an injectable
variable, and wrote a Windows-only unit suite that exercises every
fallback branch without spawning real PowerShell. Final state validated
with
go vet ./...,go build ./..., andgo test ./pkg/agent/...onWindows.
Screenshots (optional)
N/A — daemon-only change.