stablyai/orca · critical

[verify-skills-cli-runtime] ${label} emitted invalid JSON:\n

Error message

[verify-skills-cli-runtime] ${label} emitted invalid JSON:\n${output}

What it means

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.

Source

Thrown at config/scripts/verify-skills-cli-runtime.cjs:170

      result.error?.message,
      result.signal ? `terminated by ${result.signal}` : null,
      result.stdout,
      result.stderr
    ]
      .filter(Boolean)
      .join('\n')
    throw new Error(
      `[verify-skills-cli-runtime] ${args.join(' ')} exited ${String(result.status)}\n${detail}`
    )
  }
  return result.stdout
}

function parseJson(label, output) {
  try {
    return JSON.parse(output)
  } catch {
    throw new Error(`[verify-skills-cli-runtime] ${label} emitted invalid JSON:\n${output}`)
  }
}

function verifySkillsCliRuntime(outDir, artifactRoot = dirname(outDir), options = {}) {
  const absoluteOutDir = resolve(outDir)
  const closure = collectRuntimeClosure(absoluteOutDir, resolve(artifactRoot))
  if (options.executeCommands === false) {
    return { closureFiles: closure.length, commands: 0 }
  }
  const list = parseJson('skills list', runCli(absoluteOutDir, ['skills', 'list', '--json']))
  const topicNames = new Set(list.topics?.map((topic) => topic.name))
  for (const topic of ['orca-cli', 'computer-use']) {
    if (!topicNames.has(topic)) {
      throw new Error(`[verify-skills-cli-runtime] skills list omitted ${topic}`)
    }
    const guide = runCli(absoluteOutDir, ['skills', 'get', topic])
    if (!guide.includes(`name: ${topic}`)) {
      throw new Error(`[verify-skills-cli-runtime] skills get ${topic} returned the wrong guide`)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect the raw output included after 'emitted invalid JSON:' to see whether it is empty, partial, prefixed with warnings, or entirely wrong format.
  2. Ensure the CLI's --json code path writes only JSON to stdout — route all diagnostics/warnings to stderr.
  3. 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.
  4. Reproduce: node out/cli/index.js skills list --json and pipe through a JSON validator.

Example fix

// before: warning leaks to stdout before JSON
console.log('Warning: deprecated topic found')
console.log(JSON.stringify(result))

// after: diagnostics go to stderr, stdout stays pure JSON
console.error('Warning: deprecated topic found')
process.stdout.write(JSON.stringify(result))
Defensive patterns

Strategy: validation

Validate before calling

function tryParseJson(label, output) {
  const trimmed = output.trim()
  if (trimmed === '') {
    throw new Error(`${label} produced empty output`)
  }
  try {
    return JSON.parse(trimmed)
  } catch {
    // Log first 200 chars for diagnosis without leaking full output
    throw new Error(`${label} emitted non-JSON (first 200 chars): ${trimmed.slice(0, 200)}`)
  }
}

Type guard

function isValidCliJson(value, requiredKeys) {
  return value !== null && typeof value === 'object' &&
    requiredKeys.every((k) => k in value)
}

Try / catch

try {
  const list = parseJson('skills list', stdout)
} catch (err) {
  if (err.message.includes('invalid JSON')) {
    // Check if stdout has a log prefix — strip it and retry
    const jsonStart = stdout.indexOf('{')
    if (jsonStart > 0) {
      return JSON.parse(stdout.slice(jsonStart))
    }
  }
  throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/58e2f8ccd1d5b269. Report an issue: GitHub.