fix(scripts): harden Windows ZAI fallback repro

This commit is contained in:
Vincent Koc
2026-05-24 15:11:21 +02:00
parent 400d90a4da
commit 5be62e779b
3 changed files with 87 additions and 6 deletions

View File

@@ -127,6 +127,7 @@ Docs: https://docs.openclaw.ai
- Release/Windows: run release-check npm pack/install/root probes through the shared npm runner so native Windows avoids bare `npm` lookup and `.cmd` shell-argv handling.
- Release/Windows: run cross-OS release check `.cmd` shims through explicit `cmd.exe` wrapping so native Windows install and gateway probes avoid Node shell-argv handling.
- Control UI/Windows: run i18n Pi, npm, and pnpm helper commands through explicit Windows runners so native Windows translation sync avoids brittle `.cmd` launches.
- Scripts/Windows: run the Z.AI fallback repro through the shared pnpm runner so native Windows avoids raw `.cmd` launches.
- Plugins/Windows: run plugin npm package staging through the shared npm runner so native Windows release checks avoid bare `npm` lookup and `.cmd` shell-argv handling.
- Checks/Windows: route full `pnpm check` stage commands through the managed child runner so Windows avoids Node shell-argv deprecation warnings there too.
- Agents/fs: allow workspace-only host write/edit tools to write through in-workspace symlink directory parents while preserving outside-workspace symlink rejection. Fixes #84696. Thanks @garbagenetwork.

View File

@@ -3,6 +3,8 @@ import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
type RunResult = {
code: number | null;
@@ -11,6 +13,47 @@ type RunResult = {
stderr: string;
};
type PnpmCommand = {
args: string[];
command: string;
env?: NodeJS.ProcessEnv;
shell: boolean;
windowsVerbatimArguments?: boolean;
};
type ResolvePnpmCommandOptions = {
comSpec?: string;
env?: NodeJS.ProcessEnv;
execPath?: string;
npmExecPath?: string;
platform?: NodeJS.Platform;
};
function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
return key === undefined ? undefined : env[key];
}
export function resolveZaiFallbackPnpmCommand(
args: string[],
options: ResolvePnpmCommandOptions = {},
): PnpmCommand {
const env = options.env ?? process.env;
const command = resolvePnpmRunner({
comSpec: options.comSpec ?? resolveEnvValue(env, "ComSpec"),
npmExecPath: options.npmExecPath ?? env.npm_execpath,
nodeExecPath: options.execPath ?? process.execPath,
platform: options.platform,
pnpmArgs: args,
});
if (command.env === undefined) {
const invocation = { ...command };
delete invocation.env;
return invocation;
}
return command;
}
function pickAnthropicEnv(): { type: "oauth" | "api"; value: string } | null {
const oauth = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
if (oauth) {
@@ -33,9 +76,12 @@ async function runCommand(
env: NodeJS.ProcessEnv,
): Promise<RunResult> {
return await new Promise((resolve, reject) => {
const child = spawn("pnpm", args, {
env,
const command = resolveZaiFallbackPnpmCommand(args, { env });
const child = spawn(command.command, command.args, {
env: command.env ?? env,
shell: command.shell,
stdio: ["ignore", "pipe", "pipe"],
windowsVerbatimArguments: command.windowsVerbatimArguments,
});
let stdout = "";
let stderr = "";
@@ -157,7 +203,14 @@ async function main() {
process.exit(run2.code ?? 1);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
function isCliEntrypoint() {
const entrypoint = process.argv[1];
return Boolean(entrypoint && import.meta.url === pathToFileURL(path.resolve(entrypoint)).href);
}
if (isCliEntrypoint()) {
await main().catch((err) => {
console.error(err);
process.exit(1);
});
}

View File

@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { resolveZaiFallbackPnpmCommand } from "../../scripts/zai-fallback-repro.ts";
describe("zai fallback repro command resolution", () => {
it("wraps Windows pnpm.cmd without Node shell argv", () => {
expect(
resolveZaiFallbackPnpmCommand(
["openclaw", "agent", "--message", "hello world"],
{
comSpec: String.raw`C:\Windows\System32\cmd.exe`,
npmExecPath: String.raw`C:\Program Files\nodejs\pnpm.cmd`,
platform: "win32",
},
),
).toEqual({
args: [
"/d",
"/s",
"/c",
String.raw`""C:\Program Files\nodejs\pnpm.cmd" openclaw agent --message "hello world""`,
],
command: String.raw`C:\Windows\System32\cmd.exe`,
shell: false,
windowsVerbatimArguments: true,
});
});
});