{"record":{"id":"ee940a33bc51bb04","repo":"affaan-m/ECC","slug":"claude-p-failed-rc-result-returncode-stderr","errorCode":null,"errorMessage":"claude -p failed (rc={result.returncode}): stderr={result.stderr[:500]!r} stdout_tail={result.stdout[-500:]!r}","messagePattern":"claude -p failed \\(rc=(.+?)\\): stderr=(.+?) stdout_tail=(.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/skill-comply/scripts/runner.py","lineNumber":78,"sourceCode":"        text=True,\n        timeout=timeout,\n        cwd=sandbox_dir,\n    )\n\n    # claude -p returns rc=1 when --max-turns is reached, but the stream-json\n    # output is still complete and parseable. Treat this graceful termination\n    # as non-fatal so scenarios that hit the turn cap still produce usable\n    # observations.\n    nonfatal_max_turns = (\n        result.returncode == 1\n        and '\"terminal_reason\":\"max_turns\"' in result.stdout\n    )\n    if result.returncode != 0 and not nonfatal_max_turns:\n        # Include both stderr and stdout tails. claude -p often surfaces the\n        # actual failure context (model error JSON, partial stream-json) on\n        # stdout, while stderr carries generic transport / auth messages.\n        # Showing both dramatically reduces \"rc=N: <empty>\" debugging dead-ends.\n        raise RuntimeError(\n            f\"claude -p failed (rc={result.returncode}): \"\n            f\"stderr={result.stderr[:500]!r} stdout_tail={result.stdout[-500:]!r}\"\n        )\n\n    observations = _parse_stream_json(result.stdout)\n\n    return ScenarioRun(\n        scenario=scenario,\n        observations=tuple(observations),\n        sandbox_dir=sandbox_dir,\n    )\n\n\ndef _safe_sandbox_dir(scenario_id: str) -> Path:\n    \"\"\"Sanitize scenario ID and ensure path stays within sandbox base.\"\"\"\n    safe_id = re.sub(r\"[^a-zA-Z0-9\\-_]\", \"_\", scenario_id)\n    path = SANDBOX_BASE / safe_id\n    # Validate path stays within sandbox base (raises ValueError on traversal)","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/skill-comply/scripts/runner.py#L60-L96","documentation":"The subprocess invocation of `claude -p` exited with a non-zero return code that is not the recognized graceful 'max_turns' termination (rc=1 with terminal_reason:max_turns in stdout). The message includes both a stderr tail and a stdout tail because claude -p frequently surfaces the real failure (model error JSON, partial stream-json) on stdout while stderr carries transport/auth noise.","triggerScenarios":"Invalid or unauthorized ANTHROPIC_API_KEY; the claude binary is missing or old; --allowedTools rejected by policy; a model error returned by the API; quota exhausted; timeout (subprocess.TimeoutExpired is separate but a 124-style rc can land here too).","commonSituations":"CI without API credentials; local install of claude CLI that does not support --output-format stream-json; a stale auth token; rate limiting that returned a non-zero exit.","solutions":["Read the stderr and stdout tails in the message — they name the actual cause (auth, model, transport).","Run the same claude -p command manually in the sandbox dir to reproduce.","For auth: export ANTHROPIC_API_KEY (or claude login) and re-run.","For transport/version: upgrade the claude CLI to a version that supports stream-json.","If the failure is transient (rate limit), retry with backoff at the orchestrator level."],"exampleFix":"# before\nrun = run_scenario(scenario, model='sonnet')\n\n# after (surface + classify)\ntry:\n    run = run_scenario(scenario, model='sonnet')\nexcept RuntimeError as e:\n    msg = str(e)\n    if '401' in msg or 'authentication' in msg.lower():\n        raise SystemExit('Set ANTHROPIC_API_KEY or run `claude login`.') from e\n    if '429' in msg:\n        raise SystemExit('Rate limited; retry later.') from e\n    raise","handlingStrategy":"retry","validationCode":"import shutil\n\ndef claude_cli_ready() -> bool:\n    return shutil.which('claude') is not None","typeGuard":null,"tryCatchPattern":"from scripts.runner import run_scenario\nimport time\nfor attempt in range(3):\n    try:\n        run = run_scenario(scenario, model='sonnet')\n        break\n    except RuntimeError as e:\n        msg = str(e)\n        if '429' in msg and attempt < 2:\n            time.sleep(2 ** attempt)\n            continue\n        if '401' in msg or 'authentication' in msg.lower():\n            raise SystemExit('set ANTHROPIC_API_KEY or run claude login') from e\n        raise","preventionTips":["Assert shutil.which('claude') is not None and ANTHROPIC_API_KEY is set before starting a run.","Cap concurrent run_scenario calls to avoid self-induced rate limits.","Persist the raw claude -p command line in logs so failures can be reproduced by hand."],"tags":["subprocess","claude-cli","skill-comply","runner","diagnostics"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}