tinyhumansai/openhuman · error
Failed to parse service CLI output as JSON
Error message
Failed to parse service CLI output as JSON: ${err instanceof Error ? err.message : String(err)} What it means
parseServiceCliOutput expects the raw stdout of an openhuman-core CLI invocation to be one JSON document. This variant throws when `JSON.parse(raw)` itself fails, meaning the output was not valid JSON at all — the CLI printed human-readable text, a banner, or nothing, where the caller contract demands a machine-readable CommandResponse envelope.
Solutions
- Log/inspect the raw string passed in — what the CLI actually printed tells you which of the causes applies
- Rebuild/reinstall openhuman-core to the version matching the app (`cargo build --bin openhuman-core`, or let the desktop shell manage it) so stdout is a single JSON document
- Ensure the CLI is invoked in its machine-output mode and that log lines go to stderr, not stdout
- If stdout legitimately contains noise, extract the last parseable JSON line before handing it to parseServiceCliOutput
Example fix
// before
const resp = parseServiceCliOutput(raw);
// after (tolerate leading non-JSON log noise on stdout)
const jsonStart = raw.indexOf('{');
const resp = parseServiceCliOutput(jsonStart > 0 ? raw.slice(jsonStart) : raw); Defensive patterns
Strategy: try-catch
Validate before calling
const looksLikeJson = (raw: string): boolean =>
raw.trimStart().startsWith('{') || raw.trimStart().startsWith('[');
if (!looksLikeJson(raw)) {
// Surface the raw output for diagnosis instead of parsing
throw new Error(`Core CLI produced non-JSON output: ${raw.slice(0, 200)}`);
} Try / catch
try {
const resp = parseServiceCliOutput<MyResult>(raw);
} catch (err) {
if (err instanceof Error && err.message.includes('parse service CLI output as JSON')) {
logRawCliOutput(raw); // the raw string is the only useful diagnostic
throw new CoreCliOutputError(raw);
}
throw err;
} Prevention
- Always capture the raw CLI stdout alongside the parse error — the message alone says nothing about what was printed
- Keep CLI versions pinned to the UI build so stdout stays a single JSON document
- Route CLI diagnostics to stderr and reserve stdout for the JSON envelope
When it happens
Trigger: Invoking a core CLI subcommand through this parser when the binary writes non-JSON to stdout: a human-readable help/banner line, an error message printed to stdout instead of stderr, output truncated because the process was killed mid-write, a version skew where the CLI no longer emits pure JSON, or an empty string.
Common situations: openhuman-core binary version mismatched against the UI after a partial rebuild or update; the core process crashed and the shell captured its partial/empty stdout; a new CLI subcommand added logging to stdout; tests feeding canned string output into the parser.
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
- Failed to parse service CLI output as JSON: parsed value…
- At least one of --expression, --tz, --command, or --name…
- --auth-token must not be empty
- Cannot reveal " " on this device — it is a path on the…
- [claude_agent_sdk] claude subprocess exited with non-zero…
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/8bcf8c4ba82460ce.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/utils/tauriCommands/common.ts:92
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false;
}
const candidate = value as { result?: unknown; logs?: unknown };
if (!('result' in candidate) || !('logs' in candidate)) {
return false;
}
if (!Array.isArray(candidate.logs)) {
return false;
}
return candidate.logs.every(entry => typeof entry === 'string');
}
export function parseServiceCliOutput<T>(raw: string): CommandResponse<T> {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch (err) {
throw new Error(
`Failed to parse service CLI output as JSON: ${err instanceof Error ? err.message : String(err)}`
);
}
if (!isCommandResponse<T>(parsed)) {
throw new Error(
'Failed to parse service CLI output as JSON: parsed value does not match CommandResponse shape'
);
}
return parsed;
}
/**
* Typed marker for the CEF "IPC bridge not wired" failure mode. The vendored
* `app/src-tauri/vendor/tauri-cef/crates/tauri/scripts/ipc-protocol.js` falls
* back to `window.ipc.postMessage(...)` whenever the custom-protocol fetch
* rejects (network blip, navigation interrupt, mid-session re-entry). On CEF
* `window.ipc` is never wired — `app/src-tauri/src/cef_impl.rs` drops the
* `ipc_handler` registration — so the fallback throwsView on GitHub (pinned to 7491200858)