{"record":{"id":"afa43318739ab9b2","repo":"Graphify-Labs/graphify","slug":"claude-p-returned-a-json-array-with-no-result-obj","errorCode":null,"errorMessage":"claude -p returned a JSON array with no result object; first 500 chars of stdout: {stdout[:500]!r}","messagePattern":"claude -p returned a JSON array with no result object; first 500 chars of stdout: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"graphify/llm.py","lineNumber":1378,"sourceCode":"    carries `result`, `usage`, `modelUsage`, and `stop_reason`.\n    \"\"\"\n    try:\n        envelope = json.loads(stdout)\n    except json.JSONDecodeError as exc:\n        raise RuntimeError(\n            f\"claude -p produced unparseable JSON envelope: {exc}; \"\n            f\"first 500 chars of stdout: {stdout[:500]!r}\"\n        ) from exc\n    if isinstance(envelope, list):\n        result_events = [\n            e for e in envelope\n            if isinstance(e, dict) and e.get(\"type\") == \"result\"\n        ]\n        if result_events:\n            return result_events[-1]\n        if envelope and isinstance(envelope[-1], dict):\n            return envelope[-1]\n        raise RuntimeError(\n            \"claude -p returned a JSON array with no result object; \"\n            f\"first 500 chars of stdout: {stdout[:500]!r}\"\n        )\n    return envelope\n\n\ndef _claude_cli_error(stdout: str) -> str:\n    \"\"\"Return the CLI's own error text when the envelope flags `is_error`.\n\n    `claude -p` reports API failures (rate limits, auth) in the stdout JSON\n    envelope with `is_error: true` and leaves stderr EMPTY — and on a rate limit\n    it still exits 0. So the two obvious checks both miss it: a non-zero exit\n    printed a bare \"exited 1: \" with no cause, and a zero exit fed the error\n    string to the JSON parser, producing an empty graph that `_response_is_hollow`\n    misread as truncation and adaptive retry then bisected, re-issuing requests\n    that were still being refused (#2554). Best-effort: unparseable stdout is not\n    this function's problem, the caller's `_claude_cli_envelope` reports that.\n    \"\"\"","sourceCodeStart":1360,"sourceCodeEnd":1396,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/llm.py#L1360-L1396","documentation":"Raised by `_claude_cli_envelope` when stdout parsed as a JSON array of streamed events but no element has `\"type\": \"result\"` and the last element is not a usable dict. The newer CLI emits [system init, assistant turns, ... , result]; if the run was interrupted or the CLI changed shape, the terminal result object is missing and graphify refuses to guess which event carries the answer.","triggerScenarios":"`claude -p` streaming-format output where the final `result` event never arrived: process killed mid-stream (timeout), CLI bug/abort after assistant turns, or a future format that ends with a different event type than the last-element fallback handles.","commonSituations":"Subprocess timeout (`_resolve_api_timeout()`) cutting the CLI off before the result event; rate-limit or error events terminating the stream abnormally; early-adopted CLI pre-releases with changed event schemas.","solutions":["Re-run the extraction — transient stream interruptions usually succeed on retry.","Increase GRAPHIFY_API_TIMEOUT / --api-timeout so the CLI isn't killed before emitting the result event.","Update (or roll back) the Claude Code CLI if the stream shape changed; verify with `claude -p --output-format json \"say ok\"` that the array ends with a result object.","Inspect the first 500 chars quoted in the message to see which event types were emitted instead."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"attempts = 2\nfor i in range(attempts):\n    try:\n        result = extract_files_direct(chunk, root, backend=\"claude-cli\")\n        break\n    except RuntimeError as e:\n        if \"no result object\" not in str(e) or i == attempts - 1:\n            raise\n        # stream was cut (timeout) — retry","preventionTips":["Set GRAPHIFY_API_TIMEOUT generously for claude-cli so the subprocess isn't killed before the result event flushes.","Smoke-test `claude -p --output-format json` after every CLI update before running bulk extraction."],"tags":["claude-cli","json","subprocess","timeout","parsing"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}