JuliusBrussee/caveman · error

cannot safely launch Windows command shim: ${executable}

Error message

cannot safely launch Windows command shim: ${executable}

What it means

Thrown by portableProcessInvocation() on win32 when the resolved command is a .cmd/.bat shim but statSync shows it is not a regular file or exceeds 256 KB. The size cap is a safety guard: real npm/node shim launchers are tiny, and an oversized or non-file path is a sign the resolver found something it should not execute (or a PATH entry pointing at a directory or device with the same name).

Source

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

    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 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 = 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,
  };
}

export function forceKillTree(

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Inspect the resolved shim path named in the error: check `dir <path>` and the file size.
  2. If it is an oversized or foreign script, remove it from PATH precedence or reinstall the package that owns it (e.g. `npm reinstall` / clear node_modules and reinstall).
  3. If you control the wrapper, pass the absolute path to the real Node script (or the underlying .exe) directly instead of going through the shim.

Example fix

// before
const inv = portableProcessInvocation("my-tool", [], { platform: "win32" }); // shim resolves to 900KB wrapper.bat

// after
const inv = portableProcessInvocation("C:/proj/node_modules/.bin/my-tool.cmd", [], { platform: "win32" }); // point at the genuine small npm shim
Defensive patterns

Strategy: validation

Validate before calling

import { statSync } from "node:fs";
function isSafeShim(executable) {
  try {
    const st = statSync(executable);
    return st.isFile() && st.size <= 256 * 1024;
  } catch { return false; }
}

Try / catch

try {
  const inv = portableProcessInvocation(cmd, args, { platform: "win32" });
} catch (err) {
  if (/cannot safely launch Windows command shim/.test(err.message)) {
    // fall back to invoking the real executable path directly
  } else throw err;
}

Prevention

When it happens

Trigger: Calling portableProcessInvocation with platform win32 where PATH resolution lands on a .cmd/.bat that is actually a directory symlink target, a special file, or a large batch script (e.g. someone shipped a 1 MB self-extracting batch wrapper).

Common situations: A corrupted npm install produced a bloated .cmd shim; a PATH directory contains a same-named .bat that is a launcher for something else entirely; antivirus or a sync tool replaced the shim with a stub.

Related errors


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