{"record":{"id":"8bcf8c4ba82460ce","repo":"tinyhumansai/openhuman","slug":"failed-to-parse-service-cli-output-as-json-err","errorCode":null,"errorMessage":"Failed to parse service CLI output as JSON: ${err instanceof Error ? err.message : String(err)}","messagePattern":"Failed to parse service CLI output as JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/utils/tauriCommands/common.ts","lineNumber":92,"sourceCode":"  if (!value || typeof value !== 'object' || Array.isArray(value)) {\n    return false;\n  }\n  const candidate = value as { result?: unknown; logs?: unknown };\n  if (!('result' in candidate) || !('logs' in candidate)) {\n    return false;\n  }\n  if (!Array.isArray(candidate.logs)) {\n    return false;\n  }\n  return candidate.logs.every(entry => typeof entry === 'string');\n}\n\nexport function parseServiceCliOutput<T>(raw: string): CommandResponse<T> {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(raw);\n  } catch (err) {\n    throw new Error(\n      `Failed to parse service CLI output as JSON: ${err instanceof Error ? err.message : String(err)}`\n    );\n  }\n  if (!isCommandResponse<T>(parsed)) {\n    throw new Error(\n      'Failed to parse service CLI output as JSON: parsed value does not match CommandResponse shape'\n    );\n  }\n  return parsed;\n}\n\n/**\n * Typed marker for the CEF \"IPC bridge not wired\" failure mode. The vendored\n * `app/src-tauri/vendor/tauri-cef/crates/tauri/scripts/ipc-protocol.js` falls\n * back to `window.ipc.postMessage(...)` whenever the custom-protocol fetch\n * rejects (network blip, navigation interrupt, mid-session re-entry). On CEF\n * `window.ipc` is never wired — `app/src-tauri/src/cef_impl.rs` drops the\n * `ipc_handler` registration — so the fallback throws","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/utils/tauriCommands/common.ts#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst resp = parseServiceCliOutput(raw);\n\n// after (tolerate leading non-JSON log noise on stdout)\nconst jsonStart = raw.indexOf('{');\nconst resp = parseServiceCliOutput(jsonStart > 0 ? raw.slice(jsonStart) : raw);","handlingStrategy":"try-catch","validationCode":"const looksLikeJson = (raw: string): boolean =>\n  raw.trimStart().startsWith('{') || raw.trimStart().startsWith('[');\n\nif (!looksLikeJson(raw)) {\n  // Surface the raw output for diagnosis instead of parsing\n  throw new Error(`Core CLI produced non-JSON output: ${raw.slice(0, 200)}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const resp = parseServiceCliOutput<MyResult>(raw);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('parse service CLI output as JSON')) {\n    logRawCliOutput(raw); // the raw string is the only useful diagnostic\n    throw new CoreCliOutputError(raw);\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["cli","json","parsing","tauri","subprocess"],"backgroundTag":"invalid-json-output","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}