{"record":{"id":"0638a2a8ecc80289","repo":"JuliusBrussee/caveman","slug":"cannot-safely-launch-non-node-windows-command-shim","errorCode":null,"errorMessage":"cannot safely launch non-Node Windows command shim: ${executable}","messagePattern":"cannot safely launch non-Node Windows command shim: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/portable-process.ts","lineNumber":60,"sourceCode":"  args: readonly string[],\n  options: {\n    platform?: NodeJS.Platform;\n    env?: NodeJS.ProcessEnv;\n    execPath?: string;\n  } = {},\n): PortableInvocation {\n  const platform = options.platform ?? process.platform;\n  const env = options.env ?? process.env;\n  if (platform !== \"win32\") return { command, args: [...args] };\n  const executable = resolveWindowsCommand(command, env) ?? command;\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 = resolve(dirname(executable), ...relativeScript.split(/[\\\\/]+/));\n  if (!statSync(script).isFile()) throw new Error(`Windows command shim target is missing: ${script}`);\n  return { command: options.execPath ?? process.execPath, args: [script, ...args] };\n}\n\nexport function hostShellInvocation(\n  source: string,\n  platform: NodeJS.Platform = process.platform,\n  env: NodeJS.ProcessEnv = process.env,\n): PortableInvocation {\n  if (platform === \"win32\") {\n    return {\n      command: envValue(env, \"ComSpec\") ?? \"cmd.exe\",\n      args: [\"/d\", \"/s\", \"/c\", source],\n    };\n  }\n  return { command: \"/bin/sh\", args: [\"-c\", source] };","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/portable-process.ts#L42-L78","documentation":"After a Windows .cmd/.bat shim passes the size and file-type checks, the library parses its contents to confirm it is a Node shim (the standard npm-generated wrapper that forwards to a target .js file). Only verified Node shims are safe to relaunch via process.execPath, because executing arbitrary batch content would allow command injection. If the shim body does not match the known Node-shim shape, it throws 'cannot safely launch non-Node Windows command shim'.","triggerScenarios":"A .cmd/.bat that is a hand-written batch script (if/for/set logic) rather than an npm Node shim, a shim format from a different tool (pnpm/yarn generate different wrappers unless recognized), or an edited/obfuscated shim whose target line no longer parses.","commonSituations":"Team ships a custom .cmd wrapper for an internal CLI, a package manager upgrade changed shim generation format, or the command name collides with a system batch file found earlier on PATH.","solutions":["Replace the custom .cmd with an npm-style Node shim, or invoke node <script.js> directly and skip the .cmd entirely","Pin/align the package manager so shims are generated in the recognized format","Check PATH resolution: resolveWindowsCommand may be finding a different .cmd than you expect (where <command> in cmd.exe)","For non-Node CLIs, spawn via an explicit shell invocation (hostShellInvocation) rather than the portable path"],"exampleFix":"// before\nconst inv = portableInvocation(\"build-tool\", args); // build-tool.cmd is a hand-written batch script\n\n// after\nconst inv = portableInvocation(process.execPath, [\"./tools/build-tool.js\", ...args]);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const inv = portableInvocation(cmd, args);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"non-Node Windows command shim\")) {\n    // fallback: invoke the underlying script or an explicit shell command\n    return hostShellInvocation(`${cmd} ${args.join(\" \")}`, \"win32\", env);\n  }\n  throw e;\n}","preventionTips":["Do not hand-write .cmd wrappers for node CLIs; let a package manager generate shims","Standardize one package manager per repo so shim format stays consistent","When wrapping external non-node tools, route through hostShellInvocation deliberately"],"tags":["windows","security","process-spawn","command-injection","npm"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}