{"record":{"id":"5c7ea65ad98e5894","repo":"jackwener/OpenCLI","slug":"install-command-is-empty","errorCode":null,"errorMessage":"Install command is empty.","messagePattern":"Install command is empty\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/external.ts","lineNumber":130,"sourceCode":"export function parseCommand(cmd: string): { binary: string; args: string[] } {\n  const shellOperators = /&&|\\|\\|?|;|[><`$#\\n\\r]|\\$\\(/;\n  if (shellOperators.test(cmd)) {\n    throw new Error(\n      `Install command contains unsafe shell operators and cannot be executed securely: \"${cmd}\". ` +\n        `Please install the tool manually.`\n    );\n  }\n\n  // Tokenise respecting single- and double-quoted segments (no variable expansion).\n  const tokens: string[] = [];\n  const re = /(?:\"([^\"]*)\")|(?:'([^']*)')|(\\S+)/g;\n  let match: RegExpExecArray | null;\n  while ((match = re.exec(cmd)) !== null) {\n    tokens.push(match[1] ?? match[2] ?? match[3]);\n  }\n\n  if (tokens.length === 0) {\n    throw new Error(`Install command is empty.`);\n  }\n\n  const [binary, ...args] = tokens;\n  return { binary, args };\n}\n\nfunction shouldRetryWithCmdShim(binary: string, err: unknown): boolean {\n  const code = err instanceof Error ? (err as NodeJS.ErrnoException).code : undefined;\n  return os.platform() === 'win32' && !path.extname(binary) && code === 'ENOENT';\n}\n\nfunction runInstallCommand(cmd: string): void {\n  const { binary, args } = parseCommand(cmd);\n\n  try {\n    execFileSync(binary, args, { stdio: 'inherit' });\n  } catch (err) {\n    if (shouldRetryWithCmdShim(binary, err)) {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/external.ts#L112-L148","documentation":"parseCommand throws when tokenising the command yields zero tokens — i.e. the command string contains no actual words (empty or whitespace/quotes only). The library cannot determine a binary to run, so it fails fast.","triggerScenarios":"Calling parseCommand(''), parseCommand('   '), or a string of only quote characters; any install command whose tokenizer regex matches nothing.","commonSituations":"Empty installCommand field in apps.yaml or CLI registry config; config value that is only spaces; a template placeholder (${...}) that was stripped earlier by the shell-operator check leaving an empty string.","solutions":["Provide a non-empty install command (e.g. 'npm install -g <tool>') in the config.","Trim whitespace before saving/loading the config value to avoid whitespace-only strings.","If the tool cannot be auto-installed, install it manually so no install command is needed."],"exampleFix":"// before (apps.yaml)\ninstall: \"   \"\n// after\ninstall: \"npm install -g mytool\"","handlingStrategy":"validation","validationCode":"if (!cmd || cmd.trim().length === 0) {\n  throw new Error(\"Install command must be a non-empty string before calling parseCommand\");\n}\nparseCommand(cmd);","typeGuard":"function isNonEmptyCommand(cmd: unknown): cmd is string {\n  return typeof cmd === \"string\" && cmd.trim().length > 0;\n}","tryCatchPattern":"try {\n  const { binary } = parseCommand(cmd);\n} catch (e) {\n  if (e.message === \"Install command is empty.\") {\n    console.error(\"No install command configured; install the tool manually.\");\n  }\n}","preventionTips":["Validate installCommand is non-empty when loading config files.","Use YAML/schema validation (e.g. zod) requiring min length 1 on install fields.","Trim values before saving them to config."],"tags":["validation","config","empty-input"],"backgroundTag":"empty-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}