{"record":{"id":"58e2f8ccd1d5b269","repo":"stablyai/orca","slug":"verify-skills-cli-runtime-label-emitted-inval","errorCode":null,"errorMessage":"[verify-skills-cli-runtime] ${label} emitted invalid JSON:\\n${output}","messagePattern":"\\[verify-skills-cli-runtime\\] (.+?) emitted invalid JSON:\\\\n(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"config/scripts/verify-skills-cli-runtime.cjs","lineNumber":170,"sourceCode":"      result.error?.message,\n      result.signal ? `terminated by ${result.signal}` : null,\n      result.stdout,\n      result.stderr\n    ]\n      .filter(Boolean)\n      .join('\\n')\n    throw new Error(\n      `[verify-skills-cli-runtime] ${args.join(' ')} exited ${String(result.status)}\\n${detail}`\n    )\n  }\n  return result.stdout\n}\n\nfunction parseJson(label, output) {\n  try {\n    return JSON.parse(output)\n  } catch {\n    throw new Error(`[verify-skills-cli-runtime] ${label} emitted invalid JSON:\\n${output}`)\n  }\n}\n\nfunction verifySkillsCliRuntime(outDir, artifactRoot = dirname(outDir), options = {}) {\n  const absoluteOutDir = resolve(outDir)\n  const closure = collectRuntimeClosure(absoluteOutDir, resolve(artifactRoot))\n  if (options.executeCommands === false) {\n    return { closureFiles: closure.length, commands: 0 }\n  }\n  const list = parseJson('skills list', runCli(absoluteOutDir, ['skills', 'list', '--json']))\n  const topicNames = new Set(list.topics?.map((topic) => topic.name))\n  for (const topic of ['orca-cli', 'computer-use']) {\n    if (!topicNames.has(topic)) {\n      throw new Error(`[verify-skills-cli-runtime] skills list omitted ${topic}`)\n    }\n    const guide = runCli(absoluteOutDir, ['skills', 'get', topic])\n    if (!guide.includes(`name: ${topic}`)) {\n      throw new Error(`[verify-skills-cli-runtime] skills get ${topic} returned the wrong guide`)","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-skills-cli-runtime.cjs#L152-L188","documentation":"Thrown by verify-skills-cli-runtime.cjs parseJson() when JSON.parse() rejects the stdout of a skills CLI subcommand expected to emit JSON (skills list --json, skills install --dry-run --json, skills update --dry-run --json). The label identifies which subcommand produced the unparseable output. This catches CLI regressions where a JSON-mode command emits human-readable text, warnings, or partial output.","triggerScenarios":"runCli() returns stdout that is not valid JSON — e.g. a skills list --json command printing a deprecation warning before the JSON, a crash traceback, an empty string, or a log line interleaved with the JSON payload. The catch block rethrows with the label ('skills list', 'skills install --dry-run', 'skills update --dry-run') and the raw output.","commonSituations":"A CLI refactor that adds console.log/console.error to a --json code path; a warning printed to stdout instead of stderr before the JSON body; a command that exits 0 but writes an error message as plain text; version skew where the --json flag is silently ignored and human output is produced.","solutions":["Inspect the raw output included after 'emitted invalid JSON:' to see whether it is empty, partial, prefixed with warnings, or entirely wrong format.","Ensure the CLI's --json code path writes only JSON to stdout — route all diagnostics/warnings to stderr.","If output is empty, check that the subcommand exited 0 (runCli would have thrown error 300 first) and that --json is a supported flag for the built CLI version.","Reproduce: node out/cli/index.js skills list --json and pipe through a JSON validator."],"exampleFix":"// before: warning leaks to stdout before JSON\nconsole.log('Warning: deprecated topic found')\nconsole.log(JSON.stringify(result))\n\n// after: diagnostics go to stderr, stdout stays pure JSON\nconsole.error('Warning: deprecated topic found')\nprocess.stdout.write(JSON.stringify(result))","handlingStrategy":"validation","validationCode":"function tryParseJson(label, output) {\n  const trimmed = output.trim()\n  if (trimmed === '') {\n    throw new Error(`${label} produced empty output`)\n  }\n  try {\n    return JSON.parse(trimmed)\n  } catch {\n    // Log first 200 chars for diagnosis without leaking full output\n    throw new Error(`${label} emitted non-JSON (first 200 chars): ${trimmed.slice(0, 200)}`)\n  }\n}","typeGuard":"function isValidCliJson(value, requiredKeys) {\n  return value !== null && typeof value === 'object' &&\n    requiredKeys.every((k) => k in value)\n}","tryCatchPattern":"try {\n  const list = parseJson('skills list', stdout)\n} catch (err) {\n  if (err.message.includes('invalid JSON')) {\n    // Check if stdout has a log prefix — strip it and retry\n    const jsonStart = stdout.indexOf('{')\n    if (jsonStart > 0) {\n      return JSON.parse(stdout.slice(jsonStart))\n    }\n  }\n  throw err\n}","preventionTips":["Ensure all CLI --json code paths write only JSON to stdout; route logs to stderr.","Add a CLI integration test that asserts JSON.parse succeeds on every --json subcommand.","Avoid console.log in JSON output paths; use process.stdout.write for the payload."],"tags":["json","ci","skills-cli","stdout-pollution","verification"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}