diff --git a/CHANGELOG.md b/CHANGELOG.md index 92530d9a646..aedf20ac4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Docs: https://docs.openclaw.ai - Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k. - Discord: avoid misrouting numeric guild allowlist entries to `/channels/` by prefixing guild-only inputs with `guild:` during resolution. (#12326) Thanks @headswim. - Config: preserve `${VAR}` env references when writing config files so `openclaw config set/apply/patch` does not persist secrets to disk. Thanks @thewilloftheshadow. +- Process/Exec: avoid shell execution for `.exe` commands on Windows so env overrides work reliably in `runCommandWithTimeout`. Thanks @thewilloftheshadow. - Web tools/web_fetch: prefer `text/markdown` responses for Cloudflare Markdown for Agents, add `cf-markdown` extraction for markdown bodies, and redact fetched URLs in `x-markdown-tokens` debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42. - Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293. diff --git a/src/process/exec.ts b/src/process/exec.ts index 2670b6fc211..64d217c1034 100644 --- a/src/process/exec.ts +++ b/src/process/exec.ts @@ -116,12 +116,15 @@ export async function runCommandWithTimeout( } const stdio = resolveCommandStdio({ hasInput, preferInherit: true }); - const child = spawn(resolveCommand(argv[0]), argv.slice(1), { + const resolvedCommand = resolveCommand(argv[0] ?? ""); + const commandExt = path.extname(resolvedCommand).toLowerCase(); + const useShell = process.platform === "win32" && commandExt !== ".exe"; + const child = spawn(resolvedCommand, argv.slice(1), { stdio, cwd, env: resolvedEnv, windowsVerbatimArguments, - shell: process.platform === "win32", + shell: useShell, }); // Spawn with inherited stdin (TTY) so tools like `pi` stay interactive when needed. return await new Promise((resolve, reject) => {