Graphify-Labs/graphify · error · RuntimeError

claude -p returned a JSON array with no result object; first

Error message

claude -p returned a JSON array with no result object; first 500 chars of stdout: {stdout[:500]!r}

What it means

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.

Source

Thrown at graphify/llm.py:1378

    carries `result`, `usage`, `modelUsage`, and `stop_reason`.
    """
    try:
        envelope = json.loads(stdout)
    except json.JSONDecodeError as exc:
        raise RuntimeError(
            f"claude -p produced unparseable JSON envelope: {exc}; "
            f"first 500 chars of stdout: {stdout[:500]!r}"
        ) from exc
    if isinstance(envelope, list):
        result_events = [
            e for e in envelope
            if isinstance(e, dict) and e.get("type") == "result"
        ]
        if result_events:
            return result_events[-1]
        if envelope and isinstance(envelope[-1], dict):
            return envelope[-1]
        raise RuntimeError(
            "claude -p returned a JSON array with no result object; "
            f"first 500 chars of stdout: {stdout[:500]!r}"
        )
    return envelope


def _claude_cli_error(stdout: str) -> str:
    """Return the CLI's own error text when the envelope flags `is_error`.

    `claude -p` reports API failures (rate limits, auth) in the stdout JSON
    envelope with `is_error: true` and leaves stderr EMPTY — and on a rate limit
    it still exits 0. So the two obvious checks both miss it: a non-zero exit
    printed a bare "exited 1: " with no cause, and a zero exit fed the error
    string to the JSON parser, producing an empty graph that `_response_is_hollow`
    misread as truncation and adaptive retry then bisected, re-issuing requests
    that were still being refused (#2554). Best-effort: unparseable stdout is not
    this function's problem, the caller's `_claude_cli_envelope` reports that.
    """

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. Re-run the extraction — transient stream interruptions usually succeed on retry.
  2. Increase GRAPHIFY_API_TIMEOUT / --api-timeout so the CLI isn't killed before emitting the result event.
  3. 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.
  4. Inspect the first 500 chars quoted in the message to see which event types were emitted instead.
Defensive patterns

Strategy: retry

Try / catch

attempts = 2
for i in range(attempts):
    try:
        result = extract_files_direct(chunk, root, backend="claude-cli")
        break
    except RuntimeError as e:
        if "no result object" not in str(e) or i == attempts - 1:
            raise
        # stream was cut (timeout) — retry

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14). Data as JSON: /api/errors/afa43318739ab9b2. Report an issue: GitHub.