JuliusBrussee/caveman · error
non-Node Windows command shim: ${executable}
Error message
non-Node Windows command shim: ${executable} What it means
portableInvocation() refuses a Windows .cmd/.bat shim whose source contains no recognizable Node-launch pattern (no node/node.exe/_prog line forwarding %* to a %~dp0 script). Like its caveman-shrink twin, this is the CVE-2024-27980 mitigation: arbitrary batch files are never spawned directly; only proven Node shims are unwrapped to a direct node invocation.
Source
Thrown at agents/delegate/portable-process.mjs:41
if (existsSync(candidate)) return candidate;
}
}
return null;
}
export function portableInvocation(command, args, platform = process.platform, env = process.env) {
if (platform !== "win32") return { command, args: [...args] };
const executable = resolveWindowsCommand(command, env) ?? command;
if (!/\.(?:cmd|bat)$/i.test(executable)) return { command: executable, args: [...args] };
const stat = statSync(executable);
if (!stat.isFile() || stat.size > 256 * 1024) throw new Error(`unsafe Windows command shim: ${executable}`);
let relativeScript = null;
for (const line of readFileSync(executable, "utf8").split(/\r?\n/)) {
if (!/(?:\bnode(?:\.exe)?\b|_prog)/i.test(line) || !/%\*/.test(line)) continue;
const match = line.match(/"%(?:dp0%|~dp0)\\([^"\r\n]+\.(?:cjs|mjs|js))"\s+%\*/i);
if (match) { relativeScript = match[1]; break; }
}
if (!relativeScript) throw new Error(`non-Node Windows command shim: ${executable}`);
const script = resolve(dirname(executable), ...relativeScript.split(/[\\/]+/));
if (!statSync(script).isFile()) throw new Error(`Windows command shim target missing: ${script}`);
return { command: process.execPath, args: [script, ...args] };
}
export function delegateSpawnOptions(platform = process.platform) {
return {
detached: platform !== "win32",
windowsHide: true,
};
}
export async function killProcessTree(
child,
platform = process.platform,
taskkill = spawnSync,
kill = process.kill,
graceMs = 1500,View on GitHub (pinned to 27d5a3981a)
Solutions
- Configure the delegate command to invoke node directly with the package's JS entrypoint as the argument
- If a native binary is intended, point the command at the .exe itself, not at a .bat wrapper
- Regenerate the shim with a current npm so it uses the standard pattern
Example fix
# before
{ "command": "some-cli.cmd", "args": [] }
# after (it is a Node CLI)
{ "command": "node", "args": ["C:\\...\\some-cli\\bin\\cli.js"] }
# after (it is native)
{ "command": "C:\\...\\some-cli.exe", "args": [] } Defensive patterns
Strategy: fallback
Validate before calling
// Prefer direct-node invocation up front on Windows — avoids the shim path entirely const useDirect = process.platform === "win32";
Try / catch
try { spawn(inv.command, inv.args); }
catch (e) {
if (/non-Node Windows command shim/.test(String(e?.message))) {
spawn(realExecutableExeOrNodeEntry, args); // it is native or non-standard — bypass the shim
} else throw e;
} Prevention
- Configure delegate commands as the real executable (.exe) or node plus the JS entrypoint, never a .bat wrapper
- Standardize on npm-generated shims when a shim must be used
When it happens
Trigger: Delegate spawn targets a .cmd/.bat that wraps a non-Node binary (a native exe wrapper), or a batch file in a format npm does not generate (pnpm or yarn custom shims, hand-written launchers).
Common situations: Delegate configured to launch a CLI that ships a native binary wrapped in .bat; package-manager-specific shim formats; older npm templates that predate the _prog pattern.
Related errors
- cannot safely launch non-Node Windows command shim: ${execut
- unsafe Windows command shim: ${executable}
- cannot safely launch Windows command shim: ${executable}
- cannot safely launch non-Node Windows command shim: ${execut
- cannot safely launch Windows command shim: ${executable}
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/11f0dc5509ba3c9c.
Report an issue: GitHub.