{"record":{"id":"3cbe91ef544252e2","repo":"headroomlabs-ai/headroom","slug":"join-cmd-exceeded-the-hard-cap-s-hard-ca","errorCode":null,"errorMessage":"`{' '.join(cmd)}` exceeded the {hard_cap}s hard cap. Raise HEADROOM_LEARN_CLI_TIMEOUT_SECS for slower networks or larger digests, or try a different backend with --model <litellm-model-name>.","messagePattern":"`(.+?)` exceeded the (.+?)s hard cap\\. Raise HEADROOM_LEARN_CLI_TIMEOUT_SECS for slower networks or larger digests, or try a different backend with --model <litellm-model-name>\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"headroom/learn/analyzer.py","lineNumber":720,"sourceCode":"    stderr_lines: list[str] = []\n    final_result: str | None = None\n    eofs = 0\n\n    def _kill(reason: str) -> None:\n        proc.kill()\n        try:\n            proc.wait(timeout=5)\n        except (\n            subprocess.TimeoutExpired\n        ):  # pragma: no cover — defensive, kill normally returns fast\n            pass\n        logger.debug(\"claude-cli killed: %s\", reason)\n\n    while eofs < 2:\n        elapsed = time.monotonic() - start\n        if elapsed > hard_cap:\n            _kill(f\"hard cap {hard_cap}s exceeded\")\n            raise RuntimeError(\n                f\"`{' '.join(cmd)}` exceeded the {hard_cap}s hard cap. \"\n                \"Raise HEADROOM_LEARN_CLI_TIMEOUT_SECS for slower networks or \"\n                \"larger digests, or try a different backend with \"\n                \"--model <litellm-model-name>.\"\n            )\n        idle_elapsed = time.monotonic() - last_activity\n        if idle_elapsed > idle_cap:\n            _kill(f\"idle cap {idle_cap}s exceeded\")\n            raise RuntimeError(\n                f\"`{' '.join(cmd)}` produced no output for {idle_cap}s. \"\n                \"Check network connectivity, raise \"\n                \"HEADROOM_LEARN_CLI_IDLE_TIMEOUT_SECS, or try a different \"\n                \"backend with --model <litellm-model-name>.\"\n            )\n\n        # Block up to 1s waiting for the next event, then re-check deadlines.\n        try:\n            tag, line = events.get(timeout=1.0)","sourceCodeStart":702,"sourceCodeEnd":738,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/learn/analyzer.py#L702-L738","documentation":"Raised in the streaming claude-cli watchdog loop: every iteration checks elapsed = time.monotonic() - start against hard_cap, and once the total runtime exceeds it, the child process is killed via _kill() and this RuntimeError is raised. Unlike the idle cap (last-activity based, handled separately), the hard cap bounds TOTAL wall-clock time regardless of streaming progress; default 300s via HEADROOM_LEARN_CLI_TIMEOUT_SECS.","triggerScenarios":"`headroom learn` streaming a large digest through claude-cli that legitimately needs more than the cap (or hangs while still trickling output — the idle cap won't fire if lines keep arriving, so the hard cap is the last resort).","commonSituations":"Very large session digests with default 300s; slow networks/proxies; a previously-set low HEADROOM_LEARN_CLI_TIMEOUT_SECS lingering in the environment; the CLI rate-limited but emitting keep-alive events that defeat the idle watchdog.","solutions":["Raise the total cap: export HEADROOM_LEARN_CLI_TIMEOUT_SECS=900 (or more)","Reduce digest size / analyze shorter sessions if the run is legitimately huge","Diagnose whether the CLI is actually progressing (watch its stream events in debug logs) — if hung, fix network/auth rather than raising the cap","Switch to an API backend which has no CLI watchdog: headroom learn --model <litellm-model-name>"],"exampleFix":"# before\nheadroom learn  # streaming claude-cli killed: hard cap 300s exceeded\n\n# after\nexport HEADROOM_LEARN_CLI_TIMEOUT_SECS=1200\nheadroom learn","handlingStrategy":"retry","validationCode":"import os\ndefault_cap = 300\nif int(os.environ.get('HEADROOM_LEARN_CLI_TIMEOUT_SECS', default_cap)) < estimated_run_secs:\n    os.environ['HEADROOM_LEARN_CLI_TIMEOUT_SECS'] = str(estimated_run_secs * 2)","typeGuard":null,"tryCatchPattern":"try:\n    recs = run_learn()\nexcept RuntimeError as e:\n    if 'exceeded the' in str(e) and 'hard cap' in str(e):\n        os.environ['HEADROOM_LEARN_CLI_TIMEOUT_SECS'] = str(int(os.environ.get('HEADROOM_LEARN_CLI_TIMEOUT_SECS', '300')) * 2)\n        recs = run_learn()\n    else:\n        raise","preventionTips":["Pre-set HEADROOM_LEARN_CLI_TIMEOUT_SECS proportional to digest size","Watch stream events in debug logs to distinguish slow progress from a hang","If the CLI streams steadily but still exceeds totals, shrink the digest or switch to an API backend"],"tags":["timeout","claude-cli","streaming","watchdog","configuration"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}