{"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":"Error","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/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/utils/tauriCommands/common.ts#L74-L110","documentation":"parseServiceCliOutput() takes the raw stdout of a service CLI invocation and JSON.parses it, expecting a CommandResponse envelope. This variant is thrown when JSON.parse itself fails — the bytes on stdout are not JSON at all: human-readable error text, panic messages, log lines, shell diagnostics, or an empty string.","triggerScenarios":"The CLI binary prints errors/panics/banner text to stdout instead of pure JSON; RUST_LOG or tracing output pollutes stdout; the command fails at the shell level (missing binary, permission denied) so stdout is empty or carries shell diagnostics; the process is killed mid-write leaving truncated JSON.","commonSituations":"The service binary is missing from PATH or is a different major version than the wrapper expects; verbose logging configured to write to stdout; concurrent writes interleaving log lines into the JSON document; a half-written pipe read before process exit.","solutions":["Capture and log the raw string when parsing fails so you can see exactly what the CLI printed.","Verify the service CLI binary exists, is executable, and its version matches the frontend wrapper's contract.","Route logs to stderr or a file so stdout stays pure JSON.","Read stdout to EOF and, if the CLI emits trailing JSON documents, parse the last complete one instead of the whole stream."],"exampleFix":"// before\nconst res = parseServiceCliOutput(raw);\n// after\nlet res;\ntry {\n  res = parseServiceCliOutput(raw);\n} catch (err) {\n  console.error('[cli] raw output was not JSON:', JSON.stringify(raw.slice(0, 500)));\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"function looksLikeJson(s: string): boolean {\n  const t = s.trim();\n  return t.startsWith('{') || t.startsWith('[');\n}\n\nif (!looksLikeJson(raw)) {\n  throw new Error(`CLI emitted non-JSON stdout: ${raw.slice(0, 120)}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = parseServiceCliOutput(raw);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Failed to parse service CLI output as JSON')) {\n    console.error('[cli] raw stdout:', JSON.stringify(raw.slice(0, 500)));\n    // surface a CLI-level error, not a parse error, to the user\n  }\n  throw err;\n}","preventionTips":["Always capture raw stdout on parse failure — the bytes explain the error.","Keep service CLI logs on stderr so stdout stays pure JSON.","Pin and verify the CLI binary version against the frontend wrapper."],"tags":["json","parsing","cli","subprocess"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}