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
- 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.
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
- 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.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- [verify-skills-cli-runtime] ${args.join(' ')} exited ${Strin
- [verify-skills-cli-runtime] skills list omitted ${topic}
- [verify-skills-cli-runtime] skills get ${topic} returned the
- [verify-skills-cli-runtime] a dry-run reported execution
- [plain-node-entry-guard] daemon-entry.js was killed by ${res
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/58e2f8ccd1d5b269.
Report an issue: GitHub.