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

  1. Read the embedded stderr/stdout tail to identify the claude CLI failure (auth, bad flag, crash)
  2. Re-run the scenario once transient failures are fixed
  3. 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)