headroomlabs-ai/headroom · error · RuntimeError

`{' '.join(cmd)}` failed (exit {proc.returncode}): {stderr_b

Error message

`{' '.join(cmd)}` failed (exit {proc.returncode}):
{stderr_blob}

What it means

Raised after proc.wait() in the learn analyzer's streaming CLI path when the child process (claude-cli or a litellm-backed CLI) exits with a non-zero return code. The message embeds the command line, the exit code, and the first _MAX_SNIPPET_LEN chars of collected stderr so the underlying CLI failure (auth, quota, bad model name) is visible.

Source

Thrown at headroom/learn/analyzer.py:761

            eofs += 1
            continue
        last_activity = time.monotonic()
        if tag == "stdout":
            stdout_lines.append(line)
            event = _parse_stream_event(line)
            if event is not None and event.get("type") == "result":
                # Last result event wins if multiple are emitted.
                result_text = event.get("result")
                if isinstance(result_text, str):
                    final_result = result_text
        else:
            stderr_lines.append(line)

    proc.wait()

    if proc.returncode != 0:
        stderr_blob = "".join(stderr_lines)[:_MAX_SNIPPET_LEN]
        raise RuntimeError(f"`{' '.join(cmd)}` failed (exit {proc.returncode}):\n{stderr_blob}")

    stderr_blob = "".join(stderr_lines)
    if stderr_blob.strip():
        logger.debug("CLI stderr (exit 0): %s", stderr_blob[:_MAX_SNIPPET_LEN])

    if final_result is None:
        stdout_snippet = "".join(stdout_lines)[:_MAX_SNIPPET_LEN]
        raise RuntimeError(
            f"`{' '.join(cmd)}` did not emit a final `result` event. "
            f"First {_MAX_SNIPPET_LEN} chars of stdout:\n{stdout_snippet}"
        )

    try:
        return _strip_fenced_json(final_result)
    except json.JSONDecodeError as exc:
        snippet = final_result[:_MAX_SNIPPET_LEN]
        raise RuntimeError(
            f"`{' '.join(cmd)}` returned unparseable output. "

View on GitHub (pinned to 322425c43b)

Solutions

  1. Read the embedded stderr snippet — it names the actual CLI failure (auth, quota, model name) and dictates the fix.
  2. If auth-related: run the CLI directly (e.g. `claude` once) to complete login/token refresh, then rerun `headroom learn`.
  3. If quota/rate-limit: wait and retry, or switch backend with --model <litellm-model-name>.
  4. Verify the CLI version is compatible; reinstall/update it if the error mentions unknown arguments.
  5. Reproduce manually with the exact command shown in the message to iterate faster.

Example fix

# before: opaque failure inside headroom learn
headroom learn
# RuntimeError: `claude --output-format stream-json ...` failed (exit 1): Invalid API key ...

# after: fix the root cause the snippet names
claude login  # refresh credentials
headroom learn
Defensive patterns

Strategy: try-catch

Validate before calling

import shutil

cli = "claude"  # whatever backend headroom will invoke
if shutil.which(cli) is None:
    raise SystemExit(f"{cli} not in PATH; run auth/install before `headroom learn`")

Try / catch

try:
    result = run_learn(...)
except RuntimeError as e:
    msg = str(e)
    if "failed (exit" in msg:
        # stderr snippet is embedded after the newline; surface it to the operator
        log.error("learn CLI failed: %s", msg.split("\n", 1)[-1])
        if "auth" in msg.lower() or "api key" in msg.lower():
            raise SystemExit("Re-authenticate the CLI, then rerun") from e
    raise

Prevention

When it happens

Trigger: Any claude-cli invocation inside `headroom learn` that exits non-zero: expired or missing credentials, invalid --model flag, API 4xx/5xx surfaced by the CLI, or the CLI crashing on a malformed prompt. Happens in both the plain run() path (analyzer.py:613) and the streaming path (analyzer.py:761).

Common situations: Claude OAuth token expired and `claude` needs re-login; API quota/ billing exhausted; user passed an unknown model via --model; CLI was updated and now rejects an argument headroom passes; rate limit at exit time.

Related errors


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