JuliusBrussee/caveman · error · Error
unsafe Windows command shim
Error message
unsafe Windows command shim: ${executable} What it means
On Windows, `portableInvocation` resolves .cmd/.bat shims and, instead of executing them through cmd.exe (a command-injection and arg-mangling hazard), inspects the file to re-spawn node directly. It throws this when the shim file is not a regular file or exceeds 256KB — i.e., it fails the safety checks required to trust the shim's contents.
Solutions
- Inspect the reported shim path: verify it is a normal file and reasonably sized (dir /a, attrib)
- Reinstall the package that owns the .cmd shim (npm rebuild or npm ci) to restore a clean shim
- Remove the offending file from PATH if it is not a legitimate npm shim
- Pin a non-shim invocation (call node script.js directly) to bypass the .cmd entirely
Example fix
// before (corrupted/oversized shim at C:\proj\node_modules\.bin\tsc.cmd)
spawn("tsc", ["--build"], opts);
// after
spawn(process.execPath, ["C:/proj/node_modules/typescript/bin/tsc", "--build"], opts); Defensive patterns
Strategy: try-catch
Validate before calling
import { statSync } from "node:fs";
function shimLooksSafe(p) {
try { const s = statSync(p); return s.isFile() && s.size <= 256 * 1024; }
catch { return false; }
} Try / catch
try {
const inv = portableInvocation(command, args);
spawn(inv.command, inv.args, opts);
} catch (e) {
if (String(e.message).startsWith("unsafe Windows command shim:")) {
console.error(`Shim at ${e.message.split(": ")[1]} is corrupt/oversized; reinstall the owning package`);
} else throw e;
} Prevention
- Keep node_modules intact — avoid manual edits or symlinks in .bin
- Verify shim sizes after suspicious installs (anything near 256KB is wrong)
- Reinstall packages whose shims look tampered with
- Prefer invoking node entry scripts directly on Windows when possible
When it happens
Trigger: Spawning a Windows command whose resolved path ends in .cmd/.bat and either statSync reports it is not a regular file (directory, device, broken link) or its size is over 256 * 1024 bytes.
Common situations: A corrupted or hijacked npm shim (e.g. a .cmd replaced by a huge payload or symlinked to something odd) in node_modules/.bin; PATH resolution picking up an unexpected .cmd; antivirus or a bad install producing a non-regular file at the shim path.
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 Windows command shim
- cannot safely launch 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/f205988b394d34fe.
Report an issue: GitHub.
Appendix: source
Thrown at agents/delegate/portable-process.mjs:47
export function parseWindowsNodeShim(source) {
for (const line of source.split(/\r?\n/)) {
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,View on GitHub (pinned to 3ee70a1026)