affaan-m/ECC · error · RuntimeError

claude -p failed (rc={result.returncode}): stderr={result.st

Error message

claude -p failed (rc={result.returncode}): stderr={result.stderr[:500]!r} stdout_tail={result.stdout[-500:]!r}

What it means

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.

Source

Thrown at skills/skill-comply/scripts/runner.py:78

        text=True,
        timeout=timeout,
        cwd=sandbox_dir,
    )

    # claude -p returns rc=1 when --max-turns is reached, but the stream-json
    # output is still complete and parseable. Treat this graceful termination
    # as non-fatal so scenarios that hit the turn cap still produce usable
    # observations.
    nonfatal_max_turns = (
        result.returncode == 1
        and '"terminal_reason":"max_turns"' in result.stdout
    )
    if result.returncode != 0 and not nonfatal_max_turns:
        # Include both stderr and stdout tails. claude -p often surfaces the
        # actual failure context (model error JSON, partial stream-json) on
        # stdout, while stderr carries generic transport / auth messages.
        # Showing both dramatically reduces "rc=N: <empty>" debugging dead-ends.
        raise RuntimeError(
            f"claude -p failed (rc={result.returncode}): "
            f"stderr={result.stderr[:500]!r} stdout_tail={result.stdout[-500:]!r}"
        )

    observations = _parse_stream_json(result.stdout)

    return ScenarioRun(
        scenario=scenario,
        observations=tuple(observations),
        sandbox_dir=sandbox_dir,
    )


def _safe_sandbox_dir(scenario_id: str) -> Path:
    """Sanitize scenario ID and ensure path stays within sandbox base."""
    safe_id = re.sub(r"[^a-zA-Z0-9\-_]", "_", scenario_id)
    path = SANDBOX_BASE / safe_id
    # Validate path stays within sandbox base (raises ValueError on traversal)

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Read the stderr and stdout tails in the message — they name the actual cause (auth, model, transport).
  2. Run the same claude -p command manually in the sandbox dir to reproduce.
  3. For auth: export ANTHROPIC_API_KEY (or claude login) and re-run.
  4. For transport/version: upgrade the claude CLI to a version that supports stream-json.
  5. If the failure is transient (rate limit), retry with backoff at the orchestrator level.

Example fix

# before
run = run_scenario(scenario, model='sonnet')

# after (surface + classify)
try:
    run = run_scenario(scenario, model='sonnet')
except RuntimeError as e:
    msg = str(e)
    if '401' in msg or 'authentication' in msg.lower():
        raise SystemExit('Set ANTHROPIC_API_KEY or run `claude login`.') from e
    if '429' in msg:
        raise SystemExit('Rate limited; retry later.') from e
    raise
Defensive patterns

Strategy: retry

Validate before calling

import shutil

def claude_cli_ready() -> bool:
    return shutil.which('claude') is not None

Try / catch

from scripts.runner import run_scenario
import time
for attempt in range(3):
    try:
        run = run_scenario(scenario, model='sonnet')
        break
    except RuntimeError as e:
        msg = str(e)
        if '429' in msg and attempt < 2:
            time.sleep(2 ** attempt)
            continue
        if '401' in msg or 'authentication' in msg.lower():
            raise SystemExit('set ANTHROPIC_API_KEY or run claude login') from e
        raise

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/ee940a33bc51bb04. Report an issue: GitHub.