affaan-m/ECC · error · RuntimeError
claude -p failed (rc= ): stderr= stdout_tail=
Error message
claude -p failed (rc={result.returncode}): stderr={result.stderr[:500]!r} stdout_tail={result.stdout[-500:]!r} What it means
Subprocess guard in run_scenario: claude -p exited non-zero (and not the tolerated rc=1 max_turns case), so no usable stream-json observations were produced. stderr and the stdout tail are included to expose the underlying claude CLI failure.
Solutions
- Read the embedded stderr/stdout tail to identify the claude CLI failure (auth, bad flag, crash)
- Re-run the scenario once transient failures are fixed
- If the tail shows the turn cap was hit without the max_turns marker, raise --max-turns
Defensive patterns
Strategy: try-catch
When it happens
Trigger: Thrown at skills/skill-comply/scripts/runner.py:79 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/ee940a33bc51bb04.
Report an issue: GitHub.
Appendix: source
Thrown at skills/skill-comply/scripts/runner.py:79
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 8321021c54)