JuliusBrussee/caveman · error
cannot safely launch non-Node Windows command shim: ${execut
Error message
cannot safely launch non-Node Windows command shim: ${executable} What it means
getSpawnInvocation() in the caveman-shrink MCP server refuses to run a .cmd/.bat shim on Windows unless it can prove the shim launches a Node script. It scans the shim source for the npm-generated pattern (node/_prog plus %* forwarding to a %~dp0 script target) and, finding none, throws rather than spawn the .bat directly — the direct spawn would be the CVE-2024-27980 command-injection vector Node itself blocks.
Source
Thrown at src/mcp-servers/caveman-shrink/spawn-options.js:52
const candidate = path.join(directory, name);
if (fs.existsSync(candidate)) return candidate;
}
}
return null;
}
function getSpawnInvocation(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 source = fs.readFileSync(executable, 'utf8');
let relativeScript = null;
for (const line of source.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(`cannot safely launch non-Node Windows command shim: ${executable}`);
const script = path.resolve(path.dirname(executable), ...relativeScript.split(/[\\/]+/));
if (!fs.statSync(script).isFile()) throw new Error(`Windows command shim target is missing: ${script}`);
return { command: process.execPath, args: [script, ...args] };
}
function getSpawnOptions(platform = process.platform) {
return {
stdio: ['pipe', 'pipe', 'inherit'],
windowsHide: true,
};
}
module.exports = { getSpawnInvocation, getSpawnOptions, resolveWindowsCommand };
View on GitHub (pinned to 27d5a3981a)
Solutions
- Point the MCP server config directly at node with the JS entrypoint as the first argument instead of the .cmd shim
- Reinstall the package with the npm version the shim template comes from so the pattern matches
- If you control the shim, regenerate it via npm (npm install/rebuild) so it uses the standard node/%~dp0 forwarding
Example fix
// before (mcp config)
{ "command": "npx", "args": ["caveman-shrink"] } // resolves to a .cmd shim on Windows
// after
{ "command": "node", "args": ["C:\\path\\to\\node_modules\\caveman-shrink\\bin\\cli.mjs"] } Defensive patterns
Strategy: fallback
Validate before calling
const isWin = process.platform === "win32"; const command = isWin ? process.execPath : "npx"; const args = isWin ? [resolveToRealJsEntry()] : ["caveman-shrink"];
Try / catch
try {
spawn(cmd, args);
} catch (e) {
if (/cannot safely launch non-Node Windows command shim/.test(String(e?.message))) {
spawn(process.execPath, [realJsEntryPoint, ...args]); // fallback: direct node invocation
} else throw e;
} Prevention
- On Windows, configure MCP servers with command "node" plus the package's JS entrypoint instead of npx or .cmd shims
- Reinstall packages with current npm so shims use the standard template
- Pin package manager and npm versions consistently across the team
When it happens
Trigger: Configuring the MCP server command as npx/npm .cmd shims on win32 whose content does not match the standard npm shim template — a custom wrapper .bat, a shim generated by a non-npm package manager (pnpm/yarn custom wrappers), or an edited shim.
Common situations: Package managers generating differently-shaped shims; hand-written .bat wrappers around node scripts; older or newer npm emitting a template the regexes do not recognize.
Related errors
- unsafe Windows command shim: ${executable}
- non-Node 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/93576910c6daeafe.
Report an issue: GitHub.