JuliusBrussee/caveman · error

cannot safely launch Windows command shim: ${command}

Error message

cannot safely launch Windows command shim: ${command}

What it means

On win32, when caveman must spawn a `.cmd`/`.bat` shim it refuses to use the shell file directly (CVE-2024-27980-style command-injection risk in cmd shims). It first stats the shim; if the path is not a regular file or exceeds 256 KiB it throws this error, because parsing an oversized or non-file shim is neither safe nor useful. This is the size/existence gate before the shim-content parse.

Source

Thrown at packages/cli/src/portable-command.ts:25

  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) return match[1]!;
  }
  return null;
}

export function portableInvocation(
  command: string,
  args: readonly string[],
  platform: NodeJS.Platform = process.platform,
): PortableInvocation {
  if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(command)) {
    return { command, args: [...args] };
  }
  const stat = statSync(command);
  if (!stat.isFile() || stat.size > 256 * 1024) {
    throw new Error(`cannot safely launch Windows command shim: ${command}`);
  }
  const relativeScript = parseWindowsNodeShim(readFileSync(command, "utf8"));
  if (!relativeScript) {
    throw new Error(`cannot safely launch non-Node Windows command shim: ${command}; install a native .exe`);
  }
  const script = resolve(dirname(command), ...relativeScript.split(/[\\/]+/));
  if (!statSync(script).isFile()) {
    throw new Error(`Windows command shim target is missing: ${script}`);
  }
  return { command: process.execPath, args: [script, ...args] };
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Check the path: run `dir <command>` and confirm it is a regular file well under 256 KiB (npm shims are a few hundred bytes).
  2. Reinstall the package that owns the shim (npm reinstall / pnpm install) to regenerate a clean, small shim.
  3. Point caveman at the underlying Node script or a native .exe instead of the .cmd shim.
  4. If a wrapper script genuinely must be large, invoke node directly on your script and skip the .cmd indirection.

Example fix

# before
portableInvocation("C:\tools\mytool.cmd", args)  # mytool.cmd is 1 MiB
# after: small Node shim or direct exe
portableInvocation("C:\tools\mytool.exe", args)
Defensive patterns

Strategy: try-catch

Validate before calling

import { statSync } from "node:fs";

function shimSafeToLaunch(command: string): boolean {
  if (process.platform !== "win32" || !/\.(?:cmd|bat)$/i.test(command)) return true;
  try {
    const s = statSync(command);
    return s.isFile() && s.size <= 256 * 1024;
  } catch {
    return false;
  }
}

Try / catch

try {
  const invocation = portableInvocation(cmd, args);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("cannot safely launch Windows command shim")) {
    return spawnFallback(nativeExeFor(cmd), args); // prefer a native .exe
  }
  throw e;
}

Prevention

When it happens

Trigger: portableInvocation(command, args) on Windows where command matches /\.(cmd|bat)$/i and statSync shows a directory, a symlink/pipe rather than a plain file, or a shim file larger than 256 KiB.

Common situations: Passing a directory that merely ends in .cmd, a broken npm/pnpm install producing a bloated or malformed shim, PATH resolution landing on a stub .cmd from an unpacked archive, or antivirus replacing the shim with something huge.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/5cd3c5f518c90aa9. Report an issue: GitHub.