{"record":{"id":"065ce933744ed103","repo":"headroomlabs-ai/headroom","slug":"join-cmd-failed-exit-proc-returncode","errorCode":null,"errorMessage":"`{' '.join(cmd)}` failed (exit {proc.returncode}):\n{stderr_blob}","messagePattern":"`(.+?)` failed \\(exit (.+?)\\):\n(.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"headroom/learn/analyzer.py","lineNumber":761,"sourceCode":"            eofs += 1\n            continue\n        last_activity = time.monotonic()\n        if tag == \"stdout\":\n            stdout_lines.append(line)\n            event = _parse_stream_event(line)\n            if event is not None and event.get(\"type\") == \"result\":\n                # Last result event wins if multiple are emitted.\n                result_text = event.get(\"result\")\n                if isinstance(result_text, str):\n                    final_result = result_text\n        else:\n            stderr_lines.append(line)\n\n    proc.wait()\n\n    if proc.returncode != 0:\n        stderr_blob = \"\".join(stderr_lines)[:_MAX_SNIPPET_LEN]\n        raise RuntimeError(f\"`{' '.join(cmd)}` failed (exit {proc.returncode}):\\n{stderr_blob}\")\n\n    stderr_blob = \"\".join(stderr_lines)\n    if stderr_blob.strip():\n        logger.debug(\"CLI stderr (exit 0): %s\", stderr_blob[:_MAX_SNIPPET_LEN])\n\n    if final_result is None:\n        stdout_snippet = \"\".join(stdout_lines)[:_MAX_SNIPPET_LEN]\n        raise RuntimeError(\n            f\"`{' '.join(cmd)}` did not emit a final `result` event. \"\n            f\"First {_MAX_SNIPPET_LEN} chars of stdout:\\n{stdout_snippet}\"\n        )\n\n    try:\n        return _strip_fenced_json(final_result)\n    except json.JSONDecodeError as exc:\n        snippet = final_result[:_MAX_SNIPPET_LEN]\n        raise RuntimeError(\n            f\"`{' '.join(cmd)}` returned unparseable output. \"","sourceCodeStart":743,"sourceCodeEnd":779,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/learn/analyzer.py#L743-L779","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Read the embedded stderr snippet — it names the actual CLI failure (auth, quota, model name) and dictates the fix.","If auth-related: run the CLI directly (e.g. `claude` once) to complete login/token refresh, then rerun `headroom learn`.","If quota/rate-limit: wait and retry, or switch backend with --model <litellm-model-name>.","Verify the CLI version is compatible; reinstall/update it if the error mentions unknown arguments.","Reproduce manually with the exact command shown in the message to iterate faster."],"exampleFix":"# before: opaque failure inside headroom learn\nheadroom learn\n# RuntimeError: `claude --output-format stream-json ...` failed (exit 1): Invalid API key ...\n\n# after: fix the root cause the snippet names\nclaude login  # refresh credentials\nheadroom learn","handlingStrategy":"try-catch","validationCode":"import shutil\n\ncli = \"claude\"  # whatever backend headroom will invoke\nif shutil.which(cli) is None:\n    raise SystemExit(f\"{cli} not in PATH; run auth/install before `headroom learn`\")","typeGuard":null,"tryCatchPattern":"try:\n    result = run_learn(...)\nexcept RuntimeError as e:\n    msg = str(e)\n    if \"failed (exit\" in msg:\n        # stderr snippet is embedded after the newline; surface it to the operator\n        log.error(\"learn CLI failed: %s\", msg.split(\"\\n\", 1)[-1])\n        if \"auth\" in msg.lower() or \"api key\" in msg.lower():\n            raise SystemExit(\"Re-authenticate the CLI, then rerun\") from e\n    raise","preventionTips":["Smoke-test the backend CLI manually (one trivial invocation) before batch learn runs.","Keep credentials fresh; schedule a periodic `claude` interactive check or token refresh.","Log the full RuntimeError — the embedded stderr names the real failure."],"tags":["cli","subprocess","exit-code","learn","auth"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}