fix(coding-agent): avoid cmd.exe for git installs on windows closes #3642
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- Fixed `/copy` to avoid unbounded OSC 52 writes and clipboard races that could break terminal rendering or panic the native clipboard addon ([#3639](https://github.com/badlogic/pi-mono/issues/3639))
|
||||
- Fixed extension flag docs to show `pi.getFlag()` using registered flag names without the CLI `--` prefix ([#3614](https://github.com/badlogic/pi-mono/issues/3614))
|
||||
- Fixed provider retry/timeout settings wiring by adding `retry.provider.{timeoutMs,maxRetries,maxRetryDelayMs}`, migrating legacy `retry.maxDelayMs`, and forwarding provider controls into `streamSimple` request options ([#3627](https://github.com/badlogic/pi-mono/issues/3627))
|
||||
- Fixed Windows git package installs to bypass `cmd.exe` for native git commands, so install paths containing spaces no longer break `pi install git:...` with `fatal: Too many arguments` ([#3642](https://github.com/badlogic/pi-mono/issues/3642))
|
||||
|
||||
## [0.70.0] - 2026-04-23
|
||||
|
||||
|
||||
@@ -2323,11 +2323,28 @@ export class DefaultPackageManager implements PackageManager {
|
||||
};
|
||||
}
|
||||
|
||||
private shouldUseWindowsShell(command: string): boolean {
|
||||
if (process.platform !== "win32") {
|
||||
return false;
|
||||
}
|
||||
const commandName = basename(command).toLowerCase();
|
||||
return (
|
||||
commandName === "npm" ||
|
||||
commandName === "npx" ||
|
||||
commandName === "pnpm" ||
|
||||
commandName === "yarn" ||
|
||||
commandName === "yarnpkg" ||
|
||||
commandName === "corepack" ||
|
||||
commandName.endsWith(".cmd") ||
|
||||
commandName.endsWith(".bat")
|
||||
);
|
||||
}
|
||||
|
||||
private spawnCommand(command: string, args: string[], options?: { cwd?: string }): ChildProcess {
|
||||
return spawn(command, args, {
|
||||
cwd: options?.cwd,
|
||||
stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit",
|
||||
shell: process.platform === "win32",
|
||||
shell: this.shouldUseWindowsShell(command),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2339,7 +2356,7 @@ export class DefaultPackageManager implements PackageManager {
|
||||
return spawn(command, args, {
|
||||
cwd: options?.cwd,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: process.platform === "win32",
|
||||
shell: this.shouldUseWindowsShell(command),
|
||||
env: options?.env ? { ...process.env, ...options.env } : process.env,
|
||||
});
|
||||
}
|
||||
@@ -2406,7 +2423,7 @@ export class DefaultPackageManager implements PackageManager {
|
||||
const result = spawnSync(command, args, {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
encoding: "utf-8",
|
||||
shell: process.platform === "win32",
|
||||
shell: this.shouldUseWindowsShell(command),
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to run ${command} ${args.join(" ")}: ${result.stderr || result.stdout}`);
|
||||
|
||||
@@ -460,6 +460,20 @@ Content`,
|
||||
});
|
||||
});
|
||||
|
||||
describe("windows command spawning", () => {
|
||||
it("should avoid the shell for git so Windows paths with spaces stay single arguments", () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const managerWithInternals = packageManager as unknown as {
|
||||
shouldUseWindowsShell(command: string): boolean;
|
||||
};
|
||||
|
||||
expect(managerWithInternals.shouldUseWindowsShell("git")).toBe(false);
|
||||
expect(managerWithInternals.shouldUseWindowsShell("npm")).toBe(true);
|
||||
expect(managerWithInternals.shouldUseWindowsShell("pnpm")).toBe(true);
|
||||
expect(managerWithInternals.shouldUseWindowsShell("C:/Program Files/nodejs/npm.cmd")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("npmCommand", () => {
|
||||
it("should use npmCommand argv for npm installs", async () => {
|
||||
settingsManager = SettingsManager.inMemory({
|
||||
|
||||
Reference in New Issue
Block a user