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
- 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.
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
- 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.
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
- Unknown model: {model!r}. Allowed: {ALLOWED_MODELS}
- claude -p failed: {result.stderr}
- claude -p returned empty output
- claude -p failed: {result.stderr}
- README.md is missing the quick-start catalog summary
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/ee940a33bc51bb04.
Report an issue: GitHub.