Files
yetone 9fcd332b6c proc: a .cmd or .bat is run through cmd.exe with each argument quoted for it, so npm.cmd under C:\Program Files\nodejs (nvm-windows) no longer runs "C:\Program" when magpie updates Claude Code (wztlink1013 on Discord)
Windows starts a batch file as `cmd.exe /c <line>`, and Go builds that
line for a program's parser: with more than two quotes cmd.exe drops the
first and last, so `"C:\Program Files\nodejs\npm.cmd" install -g --prefix
"C:\Program Files\nodejs" …` ran C:\Program. An argument with & or | was
also left unquoted and split into a second command. Every command magpie
runs is made by proc.Command/CommandContext, so the fix is there:
cmd.exe /d /e:ON /v:OFF /s /c "<script> <args>", each argument quoted as
Rust's std does for batch files (doubled ", % broken up).

TestBatchFileInFolderWithSpace (windows) runs npm.cmd, a .bat and npm via
PATH under "Program Files" with the reporter's arguments plus & | % ^ and
trailing-\ cases, through a script that hands %* to a real program. On the
old code it fails on the Windows box with "'…\Program' 不是内部或外部命令".
On the Windows box (Windows 11, zh-CN) magpie's own updater
(howInstalled + runUpdate) on a real npm install of Claude Code 2.0.50
under "…\Program Files\nodejs": old code failed with the reporter's error,
new code updated it to 2.1.293.
2026-10-08 15:04:24 +08:00

46 lines
1.7 KiB
Go

// Package proc makes the commands magpie runs: agents' CLIs, the probes
// that ask them what they have, the system's own tools. Every one is made
// here rather than with os/exec, so that on Windows none of them opens a
// window.
//
// The desktop app on Windows is linked as a GUI program and has no console.
// A console program it starts (devin, claude, codex, cursor-agent, grok, the
// probes behind them) is given a new console by Windows, and with it a
// window, one for every run; so a child of a magpie with no console is
// started with CREATE_NO_WINDOW, which gives it a console without one. A
// magpie run in a terminal, or the desktop app started from one with a
// command, lets its children share that console as before, so Ctrl+C there
// reaches them too. Nothing magpie starts reads or writes the terminal
// itself: its output is piped back, which a hidden console doesn't change.
package proc
import (
"context"
"os/exec"
"time"
)
// Command is exec.Command, with no window of its own on Windows.
func Command(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)
batch(cmd)
hide(cmd)
return cmd
}
// CommandContext is exec.CommandContext, with no window of its own on
// Windows.
func CommandContext(ctx context.Context, name string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, name, args...)
// the context ending kills the command, but not what it started: a CLI
// that is a script running node leaves node holding the output, and
// Output waited for it however long it took (#123) — its pipes are
// closed a moment after instead
cmd.WaitDelay = waitDelay
batch(cmd)
hide(cmd)
return cmd
}
var waitDelay = 2 * time.Second