Skip to content

Commit 5be62e7

Browse files
committed
fix(scripts): harden Windows ZAI fallback repro
1 parent 400d90a commit 5be62e7

3 files changed

Lines changed: 87 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ Docs: https://docs.openclaw.ai
127127
- 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.
128128
- 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.
129129
- Control UI/Windows: run i18n Pi, npm, and pnpm helper commands through explicit Windows runners so native Windows translation sync avoids brittle `.cmd` launches.
130+
- Scripts/Windows: run the Z.AI fallback repro through the shared pnpm runner so native Windows avoids raw `.cmd` launches.
130131
- 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.
131132
- Checks/Windows: route full `pnpm check` stage commands through the managed child runner so Windows avoids Node shell-argv deprecation warnings there too.
132133
- 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.

scripts/zai-fallback-repro.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { randomUUID } from "node:crypto";
33
import fs from "node:fs/promises";
44
import os from "node:os";
55
import path from "node:path";
6+
import { pathToFileURL } from "node:url";
7+
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
68

79
type RunResult = {
810
code: number | null;
@@ -11,6 +13,47 @@ type RunResult = {
1113
stderr: string;
1214
};
1315

16+
type PnpmCommand = {
17+
args: string[];
18+
command: string;
19+
env?: NodeJS.ProcessEnv;
20+
shell: boolean;
21+
windowsVerbatimArguments?: boolean;
22+
};
23+
24+
type ResolvePnpmCommandOptions = {
25+
comSpec?: string;
26+
env?: NodeJS.ProcessEnv;
27+
execPath?: string;
28+
npmExecPath?: string;
29+
platform?: NodeJS.Platform;
30+
};
31+
32+
function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
33+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
34+
return key === undefined ? undefined : env[key];
35+
}
36+
37+
export function resolveZaiFallbackPnpmCommand(
38+
args: string[],
39+
options: ResolvePnpmCommandOptions = {},
40+
): PnpmCommand {
41+
const env = options.env ?? process.env;
42+
const command = resolvePnpmRunner({
43+
comSpec: options.comSpec ?? resolveEnvValue(env, "ComSpec"),
44+
npmExecPath: options.npmExecPath ?? env.npm_execpath,
45+
nodeExecPath: options.execPath ?? process.execPath,
46+
platform: options.platform,
47+
pnpmArgs: args,
48+
});
49+
if (command.env === undefined) {
50+
const invocation = { ...command };
51+
delete invocation.env;
52+
return invocation;
53+
}
54+
return command;
55+
}
56+
1457
function pickAnthropicEnv(): { type: "oauth" | "api"; value: string } | null {
1558
const oauth = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
1659
if (oauth) {
@@ -33,9 +76,12 @@ async function runCommand(
3376
env: NodeJS.ProcessEnv,
3477
): Promise<RunResult> {
3578
return await new Promise((resolve, reject) => {
36-
const child = spawn("pnpm", args, {
37-
env,
79+
const command = resolveZaiFallbackPnpmCommand(args, { env });
80+
const child = spawn(command.command, command.args, {
81+
env: command.env ?? env,
82+
shell: command.shell,
3883
stdio: ["ignore", "pipe", "pipe"],
84+
windowsVerbatimArguments: command.windowsVerbatimArguments,
3985
});
4086
let stdout = "";
4187
let stderr = "";
@@ -157,7 +203,14 @@ async function main() {
157203
process.exit(run2.code ?? 1);
158204
}
159205

160-
main().catch((err) => {
161-
console.error(err);
162-
process.exit(1);
163-
});
206+
function isCliEntrypoint() {
207+
const entrypoint = process.argv[1];
208+
return Boolean(entrypoint && import.meta.url === pathToFileURL(path.resolve(entrypoint)).href);
209+
}
210+
211+
if (isCliEntrypoint()) {
212+
await main().catch((err) => {
213+
console.error(err);
214+
process.exit(1);
215+
});
216+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from "vitest";
2+
import { resolveZaiFallbackPnpmCommand } from "../../scripts/zai-fallback-repro.ts";
3+
4+
describe("zai fallback repro command resolution", () => {
5+
it("wraps Windows pnpm.cmd without Node shell argv", () => {
6+
expect(
7+
resolveZaiFallbackPnpmCommand(
8+
["openclaw", "agent", "--message", "hello world"],
9+
{
10+
comSpec: String.raw`C:\Windows\System32\cmd.exe`,
11+
npmExecPath: String.raw`C:\Program Files\nodejs\pnpm.cmd`,
12+
platform: "win32",
13+
},
14+
),
15+
).toEqual({
16+
args: [
17+
"/d",
18+
"/s",
19+
"/c",
20+
String.raw`""C:\Program Files\nodejs\pnpm.cmd" openclaw agent --message "hello world""`,
21+
],
22+
command: String.raw`C:\Windows\System32\cmd.exe`,
23+
shell: false,
24+
windowsVerbatimArguments: true,
25+
});
26+
});
27+
});

0 commit comments

Comments
 (0)