JuliusBrussee/caveman · error · Error

cannot safely launch Windows command shim

Error message

cannot safely launch Windows command shim: ${executable}

What it means

On Windows, portableProcessInvocation resolves a .cmd/.bat shim for the requested command and refuses to launch it if the resolved file is not a regular file or exceeds 256 KB. This guard prevents executing oversized or non-file payloads (a known .cmd injection/abuse vector on Windows) when spawning a subprocess. The message includes the offending executable path.

Solutions

  1. Inspect the reported path: delete or replace the oversized/corrupt shim in node_modules/.bin (e.g. reinstall the package: npm ci) so a normal-sized .cmd is regenerated.
  2. Invoke the underlying tool directly (e.g. call node <script> or the .js entry) instead of going through the .cmd shim.
  3. Check PATH order to ensure the intended shim (not a bloated shadow copy) is resolved first: where <command> on Windows.
  4. If the shim is legitimately large, launch its target yourself rather than through this library — the 256 KB cap is deliberate and not configurable.

Example fix

// before (oversized npx.cmd on PATH)
spawn("npx", ["eslint"]); // throws
// after
spawn(process.execPath, [require.resolve("eslint/bin/eslint.js")]);
Defensive patterns

Strategy: fallback

Validate before calling

import { statSync } from "node:fs";
function shimIsSafe(executable) {
  if (!/\.(?:cmd|bat)$/i.test(executable)) return true;
  const s = statSync(executable);
  return s.isFile() && s.size <= 256 * 1024;
}
// check before spawning on win32

Try / catch

try {
  child = portableProcessInvocation(command, args, env, process.platform);
} catch (e) {
  if (e.message.startsWith("cannot safely launch")) {
    child = { command: process.execPath, args: [directEntryScript, ...args] }; // bypass shim
  } else throw e;
}

Prevention

When it happens

Trigger: Spawning a command on win32 whose resolveWindowsCommand() result is a .cmd or .bat file that is >256 KB or statSync reports not isFile() (e.g. a directory or symlink target shaped oddly).

Common situations: A PATH shadowed by a bloated or malicious .cmd shim; a node_modules/.bin shim generated unusually large; a shim path resolving to a directory; an environment where the "command" resolves to something like npx.cmd that grew past the size cap.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/1b19e490782dca24. Report an issue: GitHub.

Appendix: source

Thrown at packages/subagent-tax/lib/process-tree.mjs:53

    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 portableProcessInvocation(
  command,
  args,
  { platform = process.platform, env = process.env, execPath = process.execPath } = {},
) {
  if (platform !== "win32") return { command, args: [...args] };
  const executable = resolveWindowsCommand(command, env);
  if (!executable) throw Object.assign(new Error(`command not found: ${command}`), { code: "ENOENT" });
  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(`cannot safely launch Windows command shim: ${executable}`);
  }
  const relativeScript = parseWindowsNodeShim(readFileSync(executable, "utf8"));
  if (!relativeScript) {
    throw new Error(`cannot safely launch 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 is missing: ${script}`);
  return { command: execPath, args: [script, ...args] };
}

export function harnessSpawnOptions(platform = process.platform) {
  return {
    detached: platform !== "win32",
    windowsHide: true,
  };
}

View on GitHub (pinned to 3ee70a1026)