{"record":{"id":"f5e3a040af9d3ad0","repo":"JuliusBrussee/caveman","slug":"enoent","errorCode":"ENOENT","errorMessage":"command not found: ${command}","messagePattern":"command not found: (.+?)","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/subagent-tax/lib/process-tree.mjs","lineNumber":49,"sourceCode":"    if (!/(?:\\bnode(?:\\.exe)?\\b|_prog)/i.test(line) || !/%\\*/.test(line)) continue;\n    // Shim-relative target (npm cmd-shim, pnpm/yarn-classic @zkochan forms), or\n    // a drive-absolute target (pnpm emits one when the global bin dir and the\n    // store sit on different drives — path.relative crosses drives as absolute).\n    const match = line.match(/\"%(?:dp0%|~dp0)\\\\([^\"\\r\\n]+\\.(?:cjs|mjs|js))\"\\s+%\\*/i)\n      || line.match(/\"([A-Za-z]:[\\\\/][^\"\\r\\n]+\\.(?:cjs|mjs|js))\"\\s+%\\*/i);\n    if (match) return match[1];\n  }\n  return null;\n}\n\nexport function portableProcessInvocation(\n  command,\n  args,\n  { platform = process.platform, env = process.env, execPath = process.execPath } = {},\n) {\n  if (platform !== \"win32\") return { command, args: [...args] };\n  const executable = resolveWindowsCommand(command, env);\n  if (!executable) throw Object.assign(new Error(`command not found: ${command}`), { code: \"ENOENT\" });\n  if (!/\\.(?:cmd|bat)$/i.test(executable)) return { command: executable, args: [...args] };\n  const stat = statSync(executable);\n  if (!stat.isFile() || stat.size > 256 * 1024) {\n    throw new Error(`cannot safely launch Windows command shim: ${executable}`);\n  }\n  const relativeScript = parseWindowsNodeShim(readFileSync(executable, \"utf8\"));\n  if (!relativeScript) {\n    throw new Error(`cannot safely launch non-Node Windows command shim: ${executable}`);\n  }\n  const script = /^[A-Za-z]:[\\\\/]/.test(relativeScript)\n    ? relativeScript\n    : resolve(dirname(executable), ...relativeScript.split(/[\\\\/]+/));\n  if (!statSync(script).isFile()) throw new Error(`Windows command shim target is missing: ${script}`);\n  return { command: execPath, args: [script, ...args] };\n}\n\nexport function harnessSpawnOptions(platform = process.platform) {\n  return {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/subagent-tax/lib/process-tree.mjs#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Install the missing executable or add its directory to PATH so resolveWindowsCommand can find it","Pass an absolute path to the executable instead of a bare command name","Verify the env option passed to portableProcessInvocation includes a usable PATH/PATHEXT (it defaults to process.env)","On non-Windows platforms this branch never fires; check that platform detection is correct if you expected POSIX behavior"],"exampleFix":"// before\nawait spawnTree({ command: \"node\", args: [\"agent.mjs\"] }); // throws on Windows without node on PATH\n// after\nconst node = process.execPath; // guaranteed absolute path to node\nawait spawnTree({ command: node, args: [\"agent.mjs\"], env: process.env });","handlingStrategy":"validation","validationCode":"import { statSync } from \"node:fs\";\nfunction assertCommandAvailable(command, env = process.env) {\n  if (process.platform !== \"win32\" || Path.isAbsolute(command)) return;\n  const found = env.PATH?.split(\";\").some((dir) => {\n    try { statSync(`${dir}\\\\${command}.cmd`); return true; } catch { return false; }\n  });\n  if (!found) throw new Error(`command not on PATH: ${command}`);\n}","typeGuard":"function isExecutableAvailable(p) {\n  try { return statSync(p).isFile(); } catch { return false; }\n}","tryCatchPattern":"try {\n  const resolved = await portableProcessInvocation({ command, args });\n} catch (err) {\n  if (err.code === \"ENOENT\") {\n    console.error(`Executable '${command}' not found; install it or add it to PATH`);\n  }\n  throw err;\n}","preventionTips":["Use absolute paths (process.execPath for node) instead of bare command names when spawning","Verify required CLIs are installed in CI images and Dockerfiles","Never construct a stripped env object without PATH for spawned processes","Pre-flight check executables with `where`/`which` during setup"],"tags":["windows","spawn","process","path"],"backgroundTag":"command-not-found","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}