diff --git a/src/agents/skills/config.ts b/src/agents/skills/config.ts index c053f2b39..a6287f7d8 100644 --- a/src/agents/skills/config.ts +++ b/src/agents/skills/config.ts @@ -77,13 +77,16 @@ export function isBundledSkillAllowed(entry: SkillEntry, allowlist?: string[]): export function hasBinary(bin: string): boolean { const pathEnv = process.env.PATH ?? ""; const parts = pathEnv.split(path.delimiter).filter(Boolean); + const extensions = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""]; for (const part of parts) { - const candidate = path.join(part, bin); - try { - fs.accessSync(candidate, fs.constants.X_OK); - return true; - } catch { - // keep scanning + for (const ext of extensions) { + const candidate = path.join(part, bin + ext); + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + // keep scanning + } } } return false; diff --git a/src/hooks/config.ts b/src/hooks/config.ts index 8a1da761d..0cdb17481 100644 --- a/src/hooks/config.ts +++ b/src/hooks/config.ts @@ -54,13 +54,16 @@ export function resolveRuntimePlatform(): string { export function hasBinary(bin: string): boolean { const pathEnv = process.env.PATH ?? ""; const parts = pathEnv.split(path.delimiter).filter(Boolean); + const extensions = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""]; for (const part of parts) { - const candidate = path.join(part, bin); - try { - fs.accessSync(candidate, fs.constants.X_OK); - return true; - } catch { - // keep scanning + for (const ext of extensions) { + const candidate = path.join(part, bin + ext); + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + // keep scanning + } } } return false; diff --git a/src/process/exec.ts b/src/process/exec.ts index 44f8b2ce0..371302e31 100644 --- a/src/process/exec.ts +++ b/src/process/exec.ts @@ -73,7 +73,12 @@ export async function runCommandWithTimeout( return false; })(); - const resolvedEnv = env ? { ...process.env, ...env } : { ...process.env }; + const mergedEnv = env ? { ...process.env, ...env } : { ...process.env }; + const resolvedEnv = Object.fromEntries( + Object.entries(mergedEnv) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => [key, String(value)]), + ); if (shouldSuppressNpmFund) { if (resolvedEnv.NPM_CONFIG_FUND == null) resolvedEnv.NPM_CONFIG_FUND = "false"; if (resolvedEnv.npm_config_fund == null) resolvedEnv.npm_config_fund = "false"; @@ -85,6 +90,7 @@ export async function runCommandWithTimeout( cwd, env: resolvedEnv, windowsVerbatimArguments, + shell: process.platform === "win32", }); // Spawn with inherited stdin (TTY) so tools like `pi` stay interactive when needed. return await new Promise((resolve, reject) => {