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

Thrown by portableProcessInvocation() on win32 when the .cmd/.bat shim was read successfully but parseWindowsNodeShim() cannot extract a relative script path from it. The function only launches shims it can prove are standard Node/npm wrappers (which reference a node script with %~dp0-relative logic); anything else — a plain batch file, a custom launcher, a shell script named .cmd — is rejected rather than executed, because executing arbitrary batch content indirectly is unsafe.

Source

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

  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(
  child,
  {
    platform = process.platform,
    kill = process.kill,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Bypass the shim: pass the absolute path to the underlying executable or Node script as command (Node scripts can be launched as [execPath, script, ...args] yourself).
  2. If the tool is npm-distributed, reinstall it with npm so a standard npm .cmd shim is generated.
  3. If you maintain the shim, rewrite it as a standard npm-style Node wrapper so parseWindowsNodeShim can extract the script target.

Example fix

// before
const inv = portableProcessInvocation("pnpm", ["run", "build"], { platform: "win32" }); // non-npm shim -> throws

// after
const inv = { command: process.execPath,
  args: ["C:/proj/node_modules/pnpm/bin/pnpm.cjs", "run", "build"] };
Defensive patterns

Strategy: fallback

Validate before calling

import { readFileSync } from "node:fs";
function looksLikeNodeShim(executable) {
  const text = readFileSync(executable, "utf8");
  return /node(\.exe)?"? "%~dp0/i.test(text) || /node_modules/.test(text);
}

Try / catch

try {
  const inv = portableProcessInvocation(cmd, args, { platform: "win32" });
} catch (err) {
  if (/non-Node Windows command shim/.test(err.message)) {
    // fallback: launch underlying exe or [execPath, script] directly
  } else throw err;
}

Prevention

When it happens

Trigger: Calling portableProcessInvocation with platform win32 where the resolved command is a hand-written .cmd/.bat (e.g. "@echo off\nmyapp.exe %*") or a shim in a format the parser does not recognize (yarn/pnpm/pnpm-style or volta shims whose internals differ from npm's node.exe invocation pattern).

Common situations: A user installed the CLI through a non-npm package manager whose shim format differs; a project checked in a custom .cmd wrapper; the shim format changed with a newer npm/pnpm/bun release.

Related errors


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