JuliusBrussee/caveman · error · Error

ENOENT

ENOENT

Error message

command not found: ${command}

What it means

portableProcessInvocation normalizes a command+args pair for cross-platform spawning. On Windows it resolves the bare command name (e.g. 'node') to an absolute executable via resolveWindowsCommand; if nothing resolvable is found it throws an ENOENT-tagged error because spawning would fail anyway.

Solutions

  1. Install the missing executable or add its directory to PATH so resolveWindowsCommand can find it
  2. Pass an absolute path to the executable instead of a bare command name
  3. Verify the env option passed to portableProcessInvocation includes a usable PATH/PATHEXT (it defaults to process.env)
  4. On non-Windows platforms this branch never fires; check that platform detection is correct if you expected POSIX behavior

Example fix

// before
await spawnTree({ command: "node", args: ["agent.mjs"] }); // throws on Windows without node on PATH
// after
const node = process.execPath; // guaranteed absolute path to node
await spawnTree({ command: node, args: ["agent.mjs"], env: process.env });
Defensive patterns

Strategy: validation

Validate before calling

import { statSync } from "node:fs";
function assertCommandAvailable(command, env = process.env) {
  if (process.platform !== "win32" || Path.isAbsolute(command)) return;
  const found = env.PATH?.split(";").some((dir) => {
    try { statSync(`${dir}\\${command}.cmd`); return true; } catch { return false; }
  });
  if (!found) throw new Error(`command not on PATH: ${command}`);
}

Type guard

function isExecutableAvailable(p) {
  try { return statSync(p).isFile(); } catch { return false; }
}

Try / catch

try {
  const resolved = await portableProcessInvocation({ command, args });
} catch (err) {
  if (err.code === "ENOENT") {
    console.error(`Executable '${command}' not found; install it or add it to PATH`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling portableProcessInvocation (or the process-tree launcher built on it) on win32 with a command name that is not on PATH, not resolvable from env (e.g. missing PATHEXT/NODE_PATH style hints), and not an absolute path.

Common situations: Running a spawned subagent/tool on a Windows CI runner or dev box where the CLI dependency (node, npm, npx, git) is not installed or not on PATH; using a custom env object that dropped PATH; typos in the configured binary name.

Related errors


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

Appendix: source

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

    if (!/(?:\bnode(?:\.exe)?\b|_prog)/i.test(line) || !/%\*/.test(line)) continue;
    // Shim-relative target (npm cmd-shim, pnpm/yarn-classic @zkochan forms), or
    // a drive-absolute target (pnpm emits one when the global bin dir and the
    // store sit on different drives — path.relative crosses drives as absolute).
    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 {

View on GitHub (pinned to 3ee70a1026)