odysseus-dev/odysseus · error · ValueError

expected input JSON to be a list of pull requests or an obje

Error message

expected input JSON to be a list of pull requests or an object with an items list

What it means

Raised by normalize_prs in scripts/pr_blocker_audit.py when the parsed input payload is neither a JSON list nor an object containing an items list. The audit pipeline accepts two shapes — a raw array of PR objects (gh pr list output) or a search-result object with items — and rejects anything else early rather than silently auditing zero PRs. Note null items is tolerated (treated as empty); the error fires only for non-list shapes like a dict or a bare string/number.

Source

Thrown at scripts/pr_blocker_audit.py:267

    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)


def missing_metadata_warning(count: int) -> str:
    noun = "PR" if count == 1 else "PRs"
    return f"Warning: {count} {noun} still missing changed-file metadata."


def normalize_pr(item: dict) -> PullRequest:
    files = tuple(sorted(set(_extract_files(item.get("files", [])))))
    title = str(item.get("title") or "")
    areas = tuple(sorted(classify_areas(files, title)))
    return PullRequest(
        number=_safe_int(item.get("number")),

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Inspect the top-level type of your input: jq 'type' file.json — it must be 'array' or an object with an items array.
  2. Wrap a single PR object in a list: echo "[$(cat pr.json)]" or jq '[.]'.
  3. Rename your custom list key to items: jq '{items: .myList}' in.json > fixed.json.
  4. Re-export from the documented source shape: gh pr list --json number,title,... > prs.json.

Example fix

# before
$ echo '{"number":42,"title":"x"}' > pr.json && python scripts/pr_blocker_audit.py --input pr.json
ValueError: expected input JSON to be a list of pull requests or an object with an items list

# after
$ jq '[.]' pr.json > pr.fixed.json   # or: gh pr list --json ... > pr.fixed.json
$ python scripts/pr_blocker_audit.py --input pr.fixed.json
Defensive patterns

Strategy: type-guard

Validate before calling

def ensure_pr_list_shape(payload) -> list:
    raw = payload.get('items', []) if isinstance(payload, dict) else payload
    if raw is None:
        return []
    if not isinstance(raw, list):
        raise ValueError('top-level JSON must be a list of PRs or {"items": [...]}')
    return raw

Type guard

def is_pr_payload(payload) -> bool:
    if isinstance(payload, list):
        return all(isinstance(p, dict) for p in payload)
    if isinstance(payload, dict):
        items = payload.get('items')
        return items is None or isinstance(items, list)
    return False

Try / catch

try:
    prs = normalize_prs(payload)
except ValueError as e:
    if 'expected input JSON' in str(e):
        payload = [payload] if isinstance(payload, dict) else {'items': payload}
        prs = normalize_prs(payload)
    else:
        raise

Prevention

When it happens

Trigger: Passing a single PR object ({"number": 1, ...}) instead of a list; a dict keyed by PR number; a JSON string or number as the top-level document; an object whose list lives under a different key than items (e.g. data or pulls); a gh api response envelope the normalizer does not know.

Common situations: Feeding a one-PR capture saved with jq '.[0]'; using a GitHub search API response (uses items — OK) vs a REST list response (bare array — OK) vs a custom export with another key; hand-built test fixtures with the wrong top-level shape.

Related errors


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