odysseus-dev/odysseus · error · RuntimeError

{cmd[0]} exited with {result.returncode}

Error message

{cmd[0]} exited with {result.returncode}

What it means

Raised by _run_gh_json in scripts/pr_blocker_audit.py when a gh CLI subprocess (gh pr list or gh api) exits non-zero and produced no usable stderr text. The fallback message names the executable and its exit code. Because stderr is preferred, seeing this exact message means gh failed silently — typically auth, connectivity, or being killed.

Source

Thrown at scripts/pr_blocker_audit.py:255

    if isinstance(payload, dict):
        if warnings:
            payload["warnings"] = [*payload.get("warnings", []), *warnings]
        return payload
    if warnings:
        return {"items": payload, "warnings": warnings}
    return payload


def _fetch_live_pr_files(repo: str, number: int) -> list[str]:
    api_path = f"repos/{repo}/pulls/{number}/files?per_page=100"
    payload = _run_gh_json(["gh", "api", "--paginate", api_path])
    return _extract_files(payload)


def _run_gh_json(cmd: list[str]):
    result = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
    if result.returncode != 0:
        raise RuntimeError(result.stderr.strip() or f"{cmd[0]} exited with {result.returncode}")
    try:
        return json.loads(result.stdout or "[]")
    except json.JSONDecodeError as exc:
        raise RuntimeError(f"gh returned invalid JSON: {exc}") from exc


def normalize_prs(payload) -> list[PullRequest]:
    raw_prs = payload.get("items", []) if isinstance(payload, dict) else payload
    if raw_prs is None:
        raw_prs = []
    if not isinstance(raw_prs, list):
        raise ValueError("expected input JSON to be a list of pull requests or an object with an items list")
    return [normalize_pr(item) for item in raw_prs if isinstance(item, dict)]


def missing_file_metadata_count(prs: list[PullRequest]) -> int:
    return sum(1 for pr in prs if not pr.files)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Authenticate gh: gh auth login (or export GH_TOKEN=<pat>), then verify with gh auth status.
  2. Reproduce manually with the exact command from the traceback, e.g. gh pr list --repo <repo> --state open --limit 1000 --json ..., to see the real failure.
  3. Check network/proxy reachability of api.github.com from the environment running the script.
  4. Confirm the gh binary exists and is a real gh (command -v gh; gh --version).
  5. If the failure is transient (rate limit, 5xx), wait and re-run the audit — fetch is retried fresh each invocation.

Example fix

# before
$ python scripts/pr_blocker_audit.py --repo org/repo --live
RuntimeError: gh exited with 1

# after
$ gh auth status          # find expired/missing credential
$ gh auth login            # or: export GH_TOKEN=ghp_xxx
$ python scripts/pr_blocker_audit.py --repo org/repo --live
Defensive patterns

Strategy: retry

Validate before calling

import subprocess

def gh_ready() -> bool:
    try:
        r = subprocess.run(['gh', 'auth', 'status'], capture_output=True, text=True, timeout=15)
        return r.returncode == 0
    except (OSError, subprocess.TimeoutExpired):
        return False

Type guard

import shutil, subprocess

def gh_usable() -> bool:
    if not shutil.which('gh'):
        return False
    return gh_ready()

Try / catch

for attempt in range(3):
    try:
        prs = fetch_live_prs(repo)
        break
    except RuntimeError as e:
        msg = str(e)
        if 'exited with' in msg and attempt < 2:
            time.sleep(2 ** attempt)  # transient gh failure (auth expiry is NOT transient: fix gh auth)
            continue
        raise

Prevention

When it happens

Trigger: gh pr list / gh api invoked by fetch_live_prs or _fetch_live_pr_files failing with empty stderr: expired token (gh auth status shows logged out), network unreachable so gh dies early, gh not installed and a shim exiting oddly, or SIGKILL/OOM producing a bare non-zero rc.

Common situations: Long-lived audit job whose gh token expired between runs; running in CI where gh is present but GH_TOKEN env is missing/invalid; corporate proxy blocking api.github.com; gh version mismatch after brew upgrade.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/4265bf36fa6d9b92. Report an issue: GitHub.