Skip to content

fix: resolve Windows .cmd shims to underlying .exe before spawn#68819

Open
HaseebDev wants to merge 7 commits intoopenclaw:mainfrom
HaseebDev:fix/windows-spawn-claude
Open

fix: resolve Windows .cmd shims to underlying .exe before spawn#68819
HaseebDev wants to merge 7 commits intoopenclaw:mainfrom
HaseebDev:fix/windows-spawn-claude

Conversation

@HaseebDev
Copy link
Copy Markdown

Summary

  • Problem: On Windows, every provider=claude-cli spawn fails at the supervisor with spawn claude ENOENT; the
    fallback chain cascades and the user sees Embedded agent failed before reply.
  • Why it matters: Claude CLI is unusable on Windows — Discord, webchat, and any other channel routing to
    claude-cli is broken.
  • What changed: .cmd shims are now resolved to their underlying .exe (or node.exe <cli.js>) and spawned
    directly, bypassing Node's CVE-2024-27980 restriction on .cmd and cmd.exe's argument-escaping limits.
    "claude" added to the supervisor's cmdCommands allowlist.
  • What did NOT change (scope boundary): no non-Windows behavior changed, resolveChildProcessInvocation in
    exec.ts is untouched, and shims with non-npm-bin bodies fall through to the original spawn path.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: "claude" was missing from the supervisor's cmdCommands allowlist, so
    resolveWindowsCommandShim returned "claude" unchanged and spawn("claude", …) failed ENOENT. Adding
    "claude" then surfaces a second bug: Node >=18.20.2 rejects spawn("claude.cmd", …) with EINVAL
    (CVE-2024-27980), and wrapping via cmd.exe /d /s /c fails because system prompts contain \r \n & | < > ^ %
    which cmd.exe cannot parse.
  • Missing detection / guardrail: no coverage for spawning non-npm .cmd shims through the supervisor's child
    adapter on Windows.
  • Contributing context (if known): resolveNpmArgvForWindows in src/process/exec.ts already solves the
    .cmd-vs-CVE problem for npm/npx, but the supervisor's createChildAdapter does not route through
    resolveChildProcessInvocation, so it did not inherit that protection.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/process/windows-command.test.ts
  • Scenario the test should lock in: resolveWindowsCmdShimArgv rewrites argv[0] from a .cmd shim to its
    underlying .exe, prepends process.execPath for .js targets, performs PATH lookup for bare names, and
    leaves argv unchanged on non-Windows / missing file / non-matching shim body.
  • Why this is the smallest reliable guardrail: the bug lives entirely in how the supervisor resolves the command
    before spawn, so unit-testing the resolver covers it without a full Windows spawn harness.
  • Existing test that already covers this (if any): none
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • claude-cli-backed providers now spawn successfully on Windows where they previously failed with spawn claude ENOENT.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (Yes but narrowly)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: The helper reads argv[0] as a path and spawns the .exe it names. The
    path source is unchanged — it comes from OpenClaw's own resolveCommand output and process.env.PATH, same as
    before. The regex matches only the narrow npm-bin shim pattern ("%dp0%…target.(exe|js)" %*); any non-matching
    shim falls through and the original argv is spawned as-is. No new untrusted input is introduced.

Repro + Verification

Environment

  • OS: Windows 11 Pro 10.0.26200 (x64)
  • Runtime/container: Node.js v22.22.2
  • Model/provider: claude-cli (Anthropic @anthropic-ai/claude-code, claude.cmd on PATH)
  • Integration/channel (if any): Discord (also reproduces on webchat)
  • Relevant config (redacted): default, Claude CLI authenticated via claude login

Steps

  1. npm i -g openclaw on Windows.
  2. npm i -g @anthropic-ai/claude-code.
  3. openclaw gateway start, send a message via Discord.

Expected

  • Message routes to claude-cli, the CLI spawns, bot replies.

Actual

[process/supervisor] spawn failed: reason=Error: spawn claude ENOENT
[model-fallback/decision] decision=candidate_failed ... detail=spawn claude ENOENT
(repeats across opus-4-7, sonnet-4-6, opus-4-6, opus-4-5, sonnet-4-5, haiku-4-5)
Embedded agent failed before reply: spawn claude ENOENT

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

After pnpm test src/process/windows-command.test.ts: 10 passed. After live Discord run: claude.exe spawns with
an absolute path, bot replies normally, no supervisor errors.

Human Verification (required)

  • Verified scenarios: end-to-end Discord message flow on Windows 11 (fails before, succeeds after); pnpm check;
    pnpm build; bundled dist wires the new helper through correctly.
  • Edge cases checked: bare-name .cmd resolved via PATH; .js target resolved to node.exe <cli.js>; missing shim
    file and non-matching shim body both fall through unchanged.
  • What I did not verify: .bat shim bodies (only .cmd tested); non-npm shim formats (chocolatey/scoop) — these
    fall through unchanged but aren't exercised.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)

Risks and Mitigations

  • Risk: a hand-written .cmd whose body coincidentally matches the regex but also does additional batch work (env
    sets, chained commands) would be bypassed in favor of the detected target.
    • Mitigation: the regex is anchored to the quoted-target + %* shape npm/pnpm/yarn shims use; custom batch
      scripts with control flow won't match and fall through untouched.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR fixes spawn claude ENOENT failures on Windows by (1) adding \"claude\" to the cmdCommands allowlist so resolveWindowsCommandShim appends .cmd, and (2) introducing resolveWindowsCmdShimArgv which reads the shim file, extracts the underlying .exe/.js target via regex, and rewrites argv[0] before spawn — bypassing both the CVE-2024-27980 .cmd restriction and cmd.exe argument-escaping issues. Non-Windows paths and non-matching shim bodies are explicitly left unchanged.

Confidence Score: 5/5

Safe to merge; the one finding is a minor hardening suggestion that does not affect the happy path.

All platform guards, fallthrough paths, and the regex are correct. The single P2 note (bare name fallthrough to CWD-relative read) is an unlikely edge case that still degrades safely to a spawn error. The happy path is verified by 10 new unit tests and a live end-to-end run on Windows 11.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/process/windows-command.ts
Line: 36-51

Comment:
**Bare shimPath falls through to CWD-relative `readFileSync`**

When a bare `.cmd` name (no path separators) is not found anywhere in `PATH`, `shimPath` is still the original bare value (e.g. `"claude.cmd"`). The code then attempts `fs.readFileSync("claude.cmd", "utf8")`, which reads relative to the process's working directory. If a file by that name happens to exist in the CWD and matches the shim regex, an unintended target in the CWD gets spawned instead of failing cleanly.

Adding an early-return when the PATH walk ends without resolving the name to an absolute path would close the gap:

```ts
  if (!shimPath.includes("\\") && !shimPath.includes("/")) {
    const pathEnv = options?.pathEnv ?? process.env.PATH ?? "";
    let resolved = false;
    for (const dir of pathEnv.split(path.delimiter)) {
      if (!dir) continue;
      const candidate = path.join(dir, shimPath);
      if (fs.existsSync(candidate)) {
        shimPath = candidate;
        resolved = true;
        break;
      }
    }
    if (!resolved) {
      return result;
    }
  }
```

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

Reviews (1): Last reviewed commit: "fix: resolve Windows .cmd shims to under..." | Re-trigger Greptile

Comment thread src/process/windows-command.ts Outdated
Comment on lines +36 to +51
if (!shimPath.includes("\\") && !shimPath.includes("/")) {
const pathEnv = options?.pathEnv ?? process.env.PATH ?? "";
for (const dir of pathEnv.split(path.delimiter)) {
if (!dir) continue;
const candidate = path.join(dir, shimPath);
if (fs.existsSync(candidate)) {
shimPath = candidate;
break;
}
}
}
let shimContent: string;
try {
shimContent = fs.readFileSync(shimPath, "utf8");
} catch {
return result;
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.

P2 Bare shimPath falls through to CWD-relative readFileSync

When a bare .cmd name (no path separators) is not found anywhere in PATH, shimPath is still the original bare value (e.g. "claude.cmd"). The code then attempts fs.readFileSync("claude.cmd", "utf8"), which reads relative to the process's working directory. If a file by that name happens to exist in the CWD and matches the shim regex, an unintended target in the CWD gets spawned instead of failing cleanly.

Adding an early-return when the PATH walk ends without resolving the name to an absolute path would close the gap:

  if (!shimPath.includes("\\") && !shimPath.includes("/")) {
    const pathEnv = options?.pathEnv ?? process.env.PATH ?? "";
    let resolved = false;
    for (const dir of pathEnv.split(path.delimiter)) {
      if (!dir) continue;
      const candidate = path.join(dir, shimPath);
      if (fs.existsSync(candidate)) {
        shimPath = candidate;
        resolved = true;
        break;
      }
    }
    if (!resolved) {
      return result;
    }
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/windows-command.ts
Line: 36-51

Comment:
**Bare shimPath falls through to CWD-relative `readFileSync`**

When a bare `.cmd` name (no path separators) is not found anywhere in `PATH`, `shimPath` is still the original bare value (e.g. `"claude.cmd"`). The code then attempts `fs.readFileSync("claude.cmd", "utf8")`, which reads relative to the process's working directory. If a file by that name happens to exist in the CWD and matches the shim regex, an unintended target in the CWD gets spawned instead of failing cleanly.

Adding an early-return when the PATH walk ends without resolving the name to an absolute path would close the gap:

```ts
  if (!shimPath.includes("\\") && !shimPath.includes("/")) {
    const pathEnv = options?.pathEnv ?? process.env.PATH ?? "";
    let resolved = false;
    for (const dir of pathEnv.split(path.delimiter)) {
      if (!dir) continue;
      const candidate = path.join(dir, shimPath);
      if (fs.existsSync(candidate)) {
        shimPath = candidate;
        resolved = true;
        break;
      }
    }
    if (!resolved) {
      return result;
    }
  }
```

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 418e6c9392

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return resolveWindowsCommandShim({
command,
cmdCommands: ["npm", "pnpm", "yarn", "npx"],
cmdCommands: ["npm", "pnpm", "yarn", "npx", "claude"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid forcing claude to .cmd unconditionally

Adding "claude" to cmdCommands rewrites every bare claude invocation to claude.cmd. This regresses Windows setups where Claude is installed as claude.exe (or another PATHEXT-resolved executable) without a .cmd shim: spawn("claude", ...) would resolve before, but now OpenClaw always attempts claude.cmd and fails with ENOENT. Keep the original command when no shim is actually present, or only append .cmd after confirming that shim can be resolved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52550f9951

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

resolvedArgv[0] = resolveCommand(resolvedArgv[0] ?? "");
const withResolvedShim = [...params.argv];
withResolvedShim[0] = resolveCommand(withResolvedShim[0] ?? "");
const resolvedArgv = resolveWindowsCmdShimArgv(withResolvedShim);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve shim paths using the child environment PATH

createChildAdapter calls resolveWindowsCmdShimArgv without passing params.env, so shim discovery falls back to process.env.PATH instead of the PATH actually used for the spawned child. If callers provide a custom env.PATH (for sandboxed toolchains, workspace-local bins, etc.), this can resolve a different command than the child would execute—or fail to resolve the intended .cmd shim at all—causing wrong-binary execution or Windows spawn failures to persist for those runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4b69d8efc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
}
}
return process.env.PATH ?? "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor explicit PATH removal when resolving Windows shims

When createChildAdapter receives a custom env that intentionally omits PATH (or sets it to undefined), resolveEffectivePathEnv still falls back to process.env.PATH. That means resolveWindowsCmdShimArgv can resolve and rewrite argv[0] using the parent process PATH even though the spawned child will run with a different environment, leading to wrong-binary execution and bypassing PATH-based sandbox restrictions in Windows runs. If env is provided but has no PATH key, this should be treated as an empty PATH rather than inheriting the parent PATH.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6ba4d6680

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +74 to +76
} else if (!ext && !hasSeparator) {
const resolved = findCmdShimOnPath(`${first}.cmd`, options?.pathEnv ?? process.env.PATH ?? "");
if (!resolved) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve PATHEXT precedence for extensionless commands

The new !ext && !hasSeparator branch rewrites any bare command by scanning only for <name>.cmd on PATH, which does not match Windows resolution semantics. spawn("tool", ...) resolves by PATH directory order plus PATHEXT precedence (typically .EXE before .CMD), but this code can pick a later .cmd shim even when an earlier .exe should win, leading to wrong-binary execution. Fresh evidence in this revision is the newly added findCmdShimOnPath(${first}.cmd, ...) path for all extensionless commands.

Useful? React with 👍 / 👎.

Comment thread src/process/windows-command.ts Outdated
Comment on lines +28 to +31
for (const [key, value] of Object.entries(env)) {
if (key.toUpperCase() === "PATH") {
return typeof value === "string" ? value : "";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Continue scanning PATH variants after undefined match

resolveEffectiveWindowsPath returns immediately on the first case-insensitive PATH key, even when that key is non-string. With mixed-case duplicates like { PATH: undefined, Path: "C:\\custom\\bin" }, this returns "" and ignores the later valid path; meanwhile toStringEnv drops the undefined key and still passes Path to the child. That mismatch makes shim lookup run against the wrong PATH and can skip the .cmd rewrite, reintroducing Windows spawn failures for affected env merges.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ac77305c5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/process/windows-command.ts Outdated
Comment on lines +25 to +26
if (env === undefined) {
return process.env.PATH ?? "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Read parent PATH case-insensitively on Windows

When createChildAdapter is invoked without params.env (the default path), resolveWindowsPathEnv(undefined) returns only process.env.PATH. On Windows setups where the key is surfaced as Path, this can become "", so resolveWindowsCmdShimArgv cannot resolve *.cmd from PATH and leaves argv[0] as a .cmd wrapper, bringing back the same Windows spawn failures this change is meant to avoid. The parent-environment fallback should perform the same case-insensitive PATH lookup used for explicit env objects (or check both PATH and Path).

Useful? React with 👍 / 👎.

@HaseebDev
Copy link
Copy Markdown
Author

CI failure is inherited from main. The same
"Explicit undefined is unnecessary on an optional parameter" error
is red on main's own CI for commits a1f277e and 6d427f8 (no PR code
involved)

@prtags
Copy link
Copy Markdown

prtags Bot commented Apr 23, 2026

Related work from PRtags group patient-piglet-45hj

Title: Open PR duplicate: Windows claude cmd shim spawning

Number Title
#68792 fix(process): shim claude on Windows child spawns
#68819* fix: resolve Windows .cmd shims to underlying .exe before spawn
#68849 fix: add "claude" to Windows cmdCommands shim allowlist

* This PR

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 30, 2026

Codex review: found issues before merge.

Summary
This PR adds a Windows .cmd shim argv resolver for supervisor child spawns and unit tests covering .cmd to .exe/.js rewrites, PATH lookup, and non-Windows fallthrough.

Reproducibility: yes. The reporter supplied Windows 11 steps and logs, and current main still routes claude-cli through bare claude while the supervisor child adapter lacks a .cmd shim-to-entrypoint resolver for that command path.

Next step before merge
Maintainer/author should address the Windows command-resolution blockers and rebase onto current main; because this touches command execution and has security-relevant wrong-binary cases, it is not a safe autonomous repair lane.

Security
Needs attention: The diff avoids shelling through cmd.exe, but the new resolver can still choose a different Windows executable than Node/libuv would spawn.

Review findings

  • [P2] Select PATH the same way Node will — src/process/windows-command.ts:26-28
  • [P2] Honor .com before cmd shim resolution — src/process/windows-command.ts:86-87
  • [P3] Add the required changelog entry — src/process/supervisor/adapters/child.ts:38-40
Review details

Best possible solution:

Land a rebased process fix that resolves npm-style Windows .cmd shims for supervisor spawns without shelling out, while matching Node's env/PATH and .com/.exe lookup semantics and preserving current child-adapter OOM/output handling.

Do we have a high-confidence way to reproduce the issue?

Yes. The reporter supplied Windows 11 steps and logs, and current main still routes claude-cli through bare claude while the supervisor child adapter lacks a .cmd shim-to-entrypoint resolver for that command path.

Is this the best way to solve the issue?

No, not as written. Shim unwrapping is the right direction, but the implementation should first match Node/libuv command selection, add the required changelog entry, and be validated on a current-main merge result.

Full review comments:

  • [P2] Select PATH the same way Node will — src/process/windows-command.ts:26-28
    resolveWindowsPathEnv walks Object.entries in insertion order, but Node sorts Windows env keys and keeps the first case-insensitive duplicate before spawning. With duplicate PATH/Path keys, OpenClaw can inspect a shim from one PATH while the child receives another, so sort and dedupe PATH keys the same way before resolving.
    Confidence: 0.88
  • [P2] Honor .com before cmd shim resolution — src/process/windows-command.ts:86-87
    For extensionless Windows spawns, libuv checks .com and then .exe, and it does not resolve .cmd without a shell. This branch only checks for <name>.exe before selecting <name>.cmd, so a tool.com on PATH can be bypassed in favor of a shim target.
    Confidence: 0.83
  • [P3] Add the required changelog entry — src/process/supervisor/adapters/child.ts:38-40
    This wires a user-visible Windows fix, but the PR only changes process code and tests. OpenClaw policy requires user-facing fix changes to include a single-line CHANGELOG.md entry, so the release notes would miss the claude-cli Windows repair.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.86

Security concerns:

  • [medium] Duplicate PATH variants can select the wrong binary — src/process/windows-command.ts:26
    Because the resolver uses insertion order while Node sorts duplicate Windows env keys, an env object with both PATH and Path can make OpenClaw resolve a shim from a different PATH than the spawned child actually receives.
    Confidence: 0.84
  • [low] .com precedence is not preserved — src/process/windows-command.ts:86
    The extensionless-command branch only checks .exe before rewriting to .cmd, but libuv tries .com before .exe; this can bypass a direct .com executable and run a shim target instead.
    Confidence: 0.8

Acceptance criteria:

  • pnpm test src/process/windows-command.test.ts src/process/supervisor/adapters/child.test.ts
  • pnpm check:changed in Testbox after rebase/merge-result validation

What I checked:

  • Current main Claude CLI command: The bundled Anthropic CLI backend still configures claude-cli with the bare command claude, matching the reported Windows failure path. (extensions/anthropic/cli-backend.ts:31, 89f73a5ef2b0)
  • Current main supervisor shim behavior: The supervisor child adapter still only allowlists npm, pnpm, yarn, and npx, and current main has no .cmd shim-to-entrypoint resolver in this path. (src/process/supervisor/adapters/child.ts:16, 89f73a5ef2b0)
  • PR implementation surface: The PR head wires createChildAdapter through resolveWindowsCmdShimArgv and adds a resolver that reads .cmd files and rewrites to .exe or node <cli.js>. (src/process/windows-command.ts:59, 192c46a80262)
  • Node env key contract: Node's child process implementation sorts Windows env keys lexicographically and keeps the first case-insensitive duplicate before passing env pairs to the child, unlike the PR resolver's insertion-order scan.
  • libuv Windows lookup contract: libuv's Windows direct-spawn search does not use PATHEXT; for extensionless names it checks .com before .exe, so a resolver must not jump to .cmd ahead of .com.
  • Current main drift: Current main child adapter now includes Linux OOM adjustment and Windows output decoding that are not present in the PR head file, so a rebase/merge-result check is needed before landing. (src/process/supervisor/adapters/child.ts:2, 89f73a5ef2b0)

Likely related people:

  • steipete: GitHub commit history shows @steipete introduced the shared Windows command shim helper and has multiple adjacent Windows exec/spawn refactors. (role: introduced behavior and adjacent owner; confidence: high; commits: 344b2286aa72, f03d7c5a4c40, edab013e51ff; files: src/process/windows-command.ts, src/process/exec.ts, src/plugin-sdk/windows-spawn.ts)
  • vincentkoc: Recent commits by @vincentkoc touched Windows process output handling and nearby exec/child adapter behavior. (role: recent maintainer; confidence: medium; commits: e6d2c9b08005, 37099dae3e0f, 25a3d3797075; files: src/process/supervisor/adapters/child.ts, src/process/exec.ts, src/process/supervisor/adapters/child.test.ts)
  • eleqtrizit: @eleqtrizit introduced the strict Windows wrapper-resolution policy in the public SDK helper, which is the closest existing resolver contract to keep aligned with. (role: adjacent owner; confidence: medium; commits: 5874a387aefa; files: src/plugin-sdk/windows-spawn.ts, src/plugin-sdk/windows-spawn.test.ts)

Remaining risk / open question:

  • The new resolver can inspect a different PATH than Node passes to the child when duplicate Windows PATH key variants are present.
  • The bare-command branch can bypass a .com executable that libuv would run before any .cmd wrapper handling.
  • The PR head predates current child-adapter OOM and Windows output-decoding changes, so the final merge result needs rebase validation.
  • The user-facing Windows fix is missing the required changelog entry.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 89f73a5ef2b0.

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.

[Bug]: Windows: spawn claude ENOENT "claude" missing from cmdCommands shim allowlist

1 participant