JuliusBrussee/caveman · error · Error
non-Node Windows command shim
Error message
non-Node Windows command shim: ${executable} What it means
`portableInvocation` parses Windows .cmd/.bat shims looking for a Node script reference it can re-launch via process.execPath. If `parseWindowsNodeShim` cannot extract a relative script path from the shim's contents — the shim is not a recognizable Node wrapper — this error is thrown because running it through cmd.exe would be unsafe and there is no safe fallback.
Solutions
- Invoke the underlying tool directly instead of the shim (e.g. `python tool.py` or `node tool.js`)
- Regenerate the shim with npm so it is a standard Node wrapper (npm rebuild / reinstall)
- Read the .cmd yourself to find the real target and spawn that target via node
- Add a non-.cmd entry point for Windows in the package providing the tool
Example fix
// before
spawn("mytool", args, opts); // mytool.cmd is a custom batch file
// after
spawn(process.execPath, ["C:/proj/tools/mytool.js", ...args], opts); Defensive patterns
Strategy: fallback
Validate before calling
import { readFileSync, statSync } from "node:fs";
function isNodeShim(p) {
try {
if (!/\.(?:cmd|bat)$/i.test(p)) return true;
const s = statSync(p);
return s.isFile() && s.size <= 256 * 1024 &&
/\.js\b/i.test(readFileSync(p, "utf8"));
} catch { return false; }
} Try / catch
try {
const inv = portableInvocation(command, args);
spawn(inv.command, inv.args, opts);
} catch (e) {
if (String(e.message).startsWith("non-Node Windows command shim:")) {
// fall back to invoking the real target yourself
spawn("node", [resolveRealEntry(command), ...args], opts);
} else throw e;
} Prevention
- Only expect portableInvocation to re-write npm-style Node shims, not arbitrary .cmd files
- Document non-Node tools and call them directly instead of through the shim
- Regenerate shims with npm rather than hand-writing .cmd wrappers
- Keep tool entry points as plain .js scripts runnable with node
When it happens
Trigger: Calling a Windows command whose resolved executable matches /\.(cmd|bat)$/i, is a regular file under 256KB, but whose text does not match the expected Node shim pattern (no "%~dp0\\...js" style target).
Common situations: A hand-written or vendor .cmd batch file that is not an npm-generated Node wrapper; a shim generated by a different tool (e.g. pnpm/yarn with different layout); a shim wrapping a non-Node executable (Python, shell).
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- cannot safely launch non-Node Windows command shim
- cannot safely launch non-Node Windows command shim
- cannot safely launch non-Node Windows command shim
- cannot safely launch Windows command shim
- ENOENT
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/11f0dc5509ba3c9c.
Report an issue: GitHub.
Appendix: source
Thrown at agents/delegate/portable-process.mjs:49
if (!/(?:\bnode(?:\.exe)?\b|_prog)/i.test(line) || !/%\*/.test(line)) continue;
// Shim-relative target (npm cmd-shim, pnpm/yarn-classic @zkochan forms), or
// a drive-absolute target (pnpm emits one when the global bin dir and the
// store sit on different drives — path.relative crosses drives as absolute).
const match = line.match(/"%(?:dp0%|~dp0)\\([^"\r\n]+\.(?:cjs|mjs|js))"\s+%\*/i)
|| line.match(/"([A-Za-z]:[\\/][^"\r\n]+\.(?:cjs|mjs|js))"\s+%\*/i);
if (match) return match[1];
}
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}`);
const relativeScript = parseWindowsNodeShim(readFileSync(executable, "utf8"));
if (!relativeScript) throw new Error(`non-Node Windows command shim: ${executable}`);
const script = /^[A-Za-z]:[\\/]/.test(relativeScript)
? relativeScript
: 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,View on GitHub (pinned to 3ee70a1026)