headroomlabs-ai/headroom · error · RuntimeError

`{' '.join(cmd)}` returned unparseable output. First {_MAX_S

Error message

`{' '.join(cmd)}` returned unparseable output. First {_MAX_SNIPPET_LEN} chars:
{stdout_snippet}

What it means

Raised when the CLI exited 0 but its stdout could not be parsed: _strip_fenced_json(result.stdout) raised json.JSONDecodeError. headroom expects the CLI to print JSON recommendations (optionally fenced in a ```json block); anything else — prose, disclaimers, partial output, empty stdout with the payload on stderr — triggers this RuntimeError including the first _MAX_SNIPPET_LEN chars of stdout for diagnosis.

Source

Thrown at headroom/learn/analyzer.py:627

            "Check network connectivity, raise HEADROOM_LEARN_CLI_TIMEOUT_SECS, "
            "or try a different backend with --model <litellm-model-name>."
        ) from None

    if result.returncode != 0:
        stderr_snippet = (result.stderr or "")[:_MAX_SNIPPET_LEN]
        raise RuntimeError(
            f"`{' '.join(cmd)}` failed (exit {result.returncode}):\n{stderr_snippet}"
        )

    # Log stderr warnings even on success (auth refreshes, deprecation notices).
    if result.stderr and result.stderr.strip():
        logger.debug("CLI stderr (exit 0): %s", result.stderr[:_MAX_SNIPPET_LEN])

    try:
        return _strip_fenced_json(result.stdout)
    except json.JSONDecodeError as exc:
        stdout_snippet = (result.stdout or "")[:_MAX_SNIPPET_LEN]
        raise RuntimeError(
            f"`{' '.join(cmd)}` returned unparseable output. "
            f"First {_MAX_SNIPPET_LEN} chars:\n{stdout_snippet}"
        ) from exc


def _call_claude_cli_streaming(
    cmd: list[str], prompt: str, *, hard_cap: int, idle_cap: int
) -> dict:
    """Run claude-cli with stream-json output and an idle-timeout watchdog.

    Each line of stdout is one JSON event from claude (system/assistant/user/
    result). Any line resets the idle deadline. The process is killed if no
    output arrives for *idle_cap* seconds, or if total elapsed exceeds
    *hard_cap* seconds. The final ``type:"result"`` event carries the assistant
    response, which is then parsed as JSON.

    Threads (rather than ``select``) drain stdout/stderr so the watchdog works
    on Windows too, where ``select`` does not support pipe handles.

View on GitHub (pinned to 322425c43b)

Solutions

  1. Inspect the stdout snippet in the message to see what the CLI actually printed
  2. Re-run — LLM output formatting is nondeterministic; often a retry yields clean JSON
  3. Try a stronger/more compliant backend (claude over codex, or an API model via --model)
  4. Update headroom — its fence-stripping may be hardened for your CLI's output quirks; report the snippet upstream if not

Example fix

# before
headroom learn
# `codex ...` returned unparseable output. First 200 chars: "Sure! Here is..."

# after
headroom learn  # retry; if persistent:
export HEADROOM_LEARN_CLI=claude  # stricter JSON compliance
headroom learn
Defensive patterns

Strategy: retry

Validate before calling

null

Try / catch

last = None
for attempt in range(3):
    try:
        return run_learn()
    except RuntimeError as e:
        if 'unparseable output' in str(e):
            last = e
            continue  # LLM formatting is nondeterministic — retry
        raise
raise last

Prevention

When it happens

Trigger: The CLI completes successfully but returns conversational text instead of strict JSON: model ignoring the JSON-only instruction, a preamble before/after the JSON that the fence-stripper can't handle, truncated output, or the CLI emitting its own log lines to stdout instead of stderr.

Common situations: Weaker CLI backends/models wrapping JSON in explanation; CLI version changes adding banners/telemetry to stdout; prompt-digest size pushing the model to answer in prose; non-English locales adding headers; ANSI color codes polluting captured stdout.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/cf0ea6812bc5052c. Report an issue: GitHub.