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
- Inspect the top-level type of your input: jq 'type' file.json — it must be 'array' or an object with an items array.
- Wrap a single PR object in a list: echo "[$(cat pr.json)]" or jq '[.]'.
- Rename your custom list key to items: jq '{items: .myList}' in.json > fixed.json.
- 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
- Standardize on one export command (gh pr list --json ...) for snapshots so the shape never varies.
- When saving a single PR, wrap it: jq '[.]'.
- Assert payload shape right after json.load in your own tooling, before passing data on.
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
- invalid JSON in {path}: {exc.msg} at line {exc.lineno}, colu
- could not read {path}: {exc}
- gh returned invalid JSON: {exc}
- Invalid JSON
- Expected a JSON object
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/75c200635408ebbd.
Report an issue: GitHub.