Skip to content

fix: wrap bunx with cmd shim on Windows#47751

Open
choiking wants to merge 1 commit intoopenclaw:mainfrom
choiking:fix/47748-windows-update-spawn
Open

fix: wrap bunx with cmd shim on Windows#47751
choiking wants to merge 1 commit intoopenclaw:mainfrom
choiking:fix/47748-windows-update-spawn

Conversation

@choiking
Copy link
Copy Markdown

Summary\n- add to the Windows command shim list in \n- ensure wraps via with \n- add a Windows-focused regression test for the path\n\n## Why\nIssue #47748 reports Windows update failures caused by batch-command spawning behavior. The Feishu onboarding import is already fixed in the current tree, but the Windows command wrapper still only special-cased and . Extending the same shim handling to keeps Windows package-manager launches on the safe wrapper path and avoids another spawn failure class.\n\nFixes #47748\n

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 16, 2026

Greptile Summary

This PR fixes Windows spawn failures for bunx by adding it to the .cmd shim list alongside the already-handled pnpm and yarn. On Windows, bunx is installed as a batch file (bunx.cmd), so spawning it directly (without the cmd.exe wrapper) triggers an EINVAL error on patched Node 18.20.2+ (CVE-2024-27980). The one-line change to resolveCommand ensures bunx follows the same wrapper path as the other package-manager CLIs.

  • src/process/exec.ts: "bunx" added to cmdCommands in resolveCommand; resolveWindowsCommandShim will now append .cmd on Windows, triggering the cmd.exe wrapper in both runExec and runCommandWithTimeout.
  • src/process/exec.windows.test.ts: New test confirms the spawn/runCommandWithTimeout path correctly wraps bunx via cmd.exe on Windows. The test assertions are duplicated inline instead of using the existing expectCmdWrappedInvocation helper (which cannot be reused because it hard-codes "pnpm.cmd --version"). A matching runExec test for bunx is absent, unlike the symmetric coverage that exists for pnpm.

Confidence Score: 4/5

  • Safe to merge — the production logic change is a single-line addition consistent with the existing pattern; only minor test-quality gaps remain.
  • The exec.ts change is minimal and exactly mirrors how pnpm and yarn are already handled, with no risk of regression on non-Windows paths. The test is functionally correct. Score is 4 rather than 5 because the runExec path for bunx on Windows lacks test coverage, and the test helper duplication could cause maintenance drift.
  • src/process/exec.windows.test.ts — missing runExec coverage for bunx and duplicated assertion logic.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/process/exec.windows.test.ts
Line: 115-137

Comment:
**Inline assertions duplicate the existing helper**

The new test reinvents the same assertion pattern already present in `expectCmdWrappedInvocation` (lines 53-64), with the only difference being the command string (`"bunx.cmd --version"` vs the hardcoded `"pnpm.cmd --version"`). The helper is not reusable today because its inner check is hardcoded. Consider accepting the expected command substring as a parameter so both tests (and any future ones) can share the helper instead of copying the four `expect` lines each time:

```ts
function expectCmdWrappedInvocation(params: {
  captured: SpawnCall | ExecCall | undefined;
  expectedComSpec: string;
  expectedCmdSubstring: string;   // e.g. "pnpm.cmd --version" or "bunx.cmd --version"
}) {
  if (!params.captured) {
    throw new Error("expected command wrapper to be called");
  }
  expect(params.captured[0]).toBe(params.expectedComSpec);
  expect(params.captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
  expect(params.captured[1][3]).toContain(params.expectedCmdSubstring);
  expect(params.captured[2].windowsVerbatimArguments).toBe(true);
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/process/exec.windows.test.ts
Line: 137

Comment:
**Missing `runExec` coverage for `bunx`**

A parallel test exists for `pnpm` via `runExec` (the second test in the suite, lines 91-113) that exercises the `execFile` code path. The new test only covers `runCommandWithTimeout` (the `spawn` path). Because the `resolveCommand` call appears independently in both `runExec` and `runCommandWithTimeout`, adding a corresponding `runExec("bunx", ...)` test would give the same regression coverage as the existing pnpm pair, and keep the pattern symmetric.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: e3741ad

Comment on lines +115 to +137
it("wraps bunx via cmd.exe in runCommandWithTimeout on Windows", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const expectedComSpec = process.env.ComSpec ?? "cmd.exe";

spawnMock.mockImplementation(
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
);

try {
const result = await runCommandWithTimeout(["bunx", "--version"], { timeoutMs: 1000 });
expect(result.code).toBe(0);
const captured = spawnMock.mock.calls[0] as SpawnCall | undefined;
if (!captured) {
throw new Error("expected command wrapper to be called");
}
expect(captured[0]).toBe(expectedComSpec);
expect(captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
expect(captured[1][3]).toContain("bunx.cmd --version");
expect(captured[2].windowsVerbatimArguments).toBe(true);
} finally {
platformSpy.mockRestore();
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline assertions duplicate the existing helper

The new test reinvents the same assertion pattern already present in expectCmdWrappedInvocation (lines 53-64), with the only difference being the command string ("bunx.cmd --version" vs the hardcoded "pnpm.cmd --version"). The helper is not reusable today because its inner check is hardcoded. Consider accepting the expected command substring as a parameter so both tests (and any future ones) can share the helper instead of copying the four expect lines each time:

function expectCmdWrappedInvocation(params: {
  captured: SpawnCall | ExecCall | undefined;
  expectedComSpec: string;
  expectedCmdSubstring: string;   // e.g. "pnpm.cmd --version" or "bunx.cmd --version"
}) {
  if (!params.captured) {
    throw new Error("expected command wrapper to be called");
  }
  expect(params.captured[0]).toBe(params.expectedComSpec);
  expect(params.captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
  expect(params.captured[1][3]).toContain(params.expectedCmdSubstring);
  expect(params.captured[2].windowsVerbatimArguments).toBe(true);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.windows.test.ts
Line: 115-137

Comment:
**Inline assertions duplicate the existing helper**

The new test reinvents the same assertion pattern already present in `expectCmdWrappedInvocation` (lines 53-64), with the only difference being the command string (`"bunx.cmd --version"` vs the hardcoded `"pnpm.cmd --version"`). The helper is not reusable today because its inner check is hardcoded. Consider accepting the expected command substring as a parameter so both tests (and any future ones) can share the helper instead of copying the four `expect` lines each time:

```ts
function expectCmdWrappedInvocation(params: {
  captured: SpawnCall | ExecCall | undefined;
  expectedComSpec: string;
  expectedCmdSubstring: string;   // e.g. "pnpm.cmd --version" or "bunx.cmd --version"
}) {
  if (!params.captured) {
    throw new Error("expected command wrapper to be called");
  }
  expect(params.captured[0]).toBe(params.expectedComSpec);
  expect(params.captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
  expect(params.captured[1][3]).toContain(params.expectedCmdSubstring);
  expect(params.captured[2].windowsVerbatimArguments).toBe(true);
}
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

} finally {
platformSpy.mockRestore();
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing runExec coverage for bunx

A parallel test exists for pnpm via runExec (the second test in the suite, lines 91-113) that exercises the execFile code path. The new test only covers runCommandWithTimeout (the spawn path). Because the resolveCommand call appears independently in both runExec and runCommandWithTimeout, adding a corresponding runExec("bunx", ...) test would give the same regression coverage as the existing pnpm pair, and keep the pattern symmetric.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.windows.test.ts
Line: 137

Comment:
**Missing `runExec` coverage for `bunx`**

A parallel test exists for `pnpm` via `runExec` (the second test in the suite, lines 91-113) that exercises the `execFile` code path. The new test only covers `runCommandWithTimeout` (the `spawn` path). Because the `resolveCommand` call appears independently in both `runExec` and `runCommandWithTimeout`, adding a corresponding `runExec("bunx", ...)` test would give the same regression coverage as the existing pnpm pair, and keep the pattern symmetric.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant