zed-industries/zed · error · RuntimeError
unexpected response for issue {number}
Error message
unexpected response for issue {number} What it means
Raised by fetch_issue() in the triage project sync script when the GitHub REST call for a single issue returns HTTP 200 but the parsed JSON body is not an object. rest_get() already retries 429/5xx and raises for other statuses, so reaching this check means GitHub (or something on the wire) answered 200 with a payload whose shape is not an issue object.
Source
Thrown at script/triage_project_sync.py:259
created_at: datetime
reporter: str
assignees: list[str]
labels: list[str]
issue_type: str | None # e.g. "Bug", "Crash", "Meta", "Tracking", or None
is_pull_request: bool
comments: list[dict]
def parse_dt(s: str | None) -> datetime | None:
if not s:
return None
return datetime.fromisoformat(s.replace("Z", "+00:00"))
def fetch_issue(number: int) -> IssueData:
issue = rest_get(f"repos/{REPO}/issues/{number}")
if not isinstance(issue, dict):
raise RuntimeError(f"unexpected response for issue {number}")
comments = rest_get_paginated(f"repos/{REPO}/issues/{number}/comments")
created_at = parse_dt(issue["created_at"])
if created_at is None:
raise RuntimeError(f"issue {number} has no created_at")
issue_type = None
if isinstance(issue.get("type"), dict):
issue_type = issue["type"].get("name")
return IssueData(
number=number,
node_id=issue["node_id"],
title=issue["title"],
state=issue["state"],
closed_at=parse_dt(issue.get("closed_at")),
created_at=created_at,
reporter=issue["user"]["login"],
assignees=[a["login"] for a in (issue.get("assignees") or [])],
labels=[l["name"] for l in issue["labels"]],
issue_type=issue_type,View on GitHub (pinned to bc538def45)
Solutions
- Log the actual payload and type for the failing number (include {issue!r} in the message) to see what came back
- Check how number was produced upstream — an empty value turns the single-issue endpoint into the list endpoint
- Verify REPO is exactly 'owner/repo' and the token is valid so the response is a real issue object
- If a proxy is in play, curl the same URL with the same headers from that environment
Example fix
# before
issue = rest_get(f"repos/{REPO}/issues/{number}")
if not isinstance(issue, dict):
raise RuntimeError(f"unexpected response for issue {number}")
# after
issue = rest_get(f"repos/{REPO}/issues/{number}")
if not isinstance(issue, dict):
raise RuntimeError(
f"unexpected response for issue {number}: "
f"{type(issue).__name__} {str(issue)[:200]}"
) Defensive patterns
Strategy: validation
Validate before calling
# Before the sync run, fail fast on bad inputs and auth
import os, requests
def validate_sync_inputs(repo: str, numbers: list[int]) -> None:
if "/" not in repo or repo.count("/") != 1:
raise SystemExit(f"REPO must be 'owner/repo', got {repo!r}")
bad = [n for n in numbers if not isinstance(n, int) or n <= 0]
if bad:
raise SystemExit(f"invalid issue numbers (must be positive ints): {bad}")
token = os.environ.get("GITHUB_TOKEN")
if token:
r = requests.get("https://api.github.com/user",
headers={"Authorization": f"Bearer {token}"}, timeout=10)
r.raise_for_status() Type guard
def is_issue_payload(payload: object) -> bool:
"""A single-issue REST response is an object containing 'number'."""
return isinstance(payload, dict) and "number" in payload and "created_at" in payload Try / catch
try:
data = fetch_issue(number)
except RuntimeError as error:
log(f"skipping issue {number}: {error}", "WARN")
continue # one malformed issue must not abort the whole sync Prevention
- Validate REPO format and issue numbers before the loop, not per-request
- Keep the isinstance check but include a payload excerpt in the error for diagnosability
- Pre-flight the token with a /user request so auth failures surface as 401, not shape surprises
When it happens
Trigger: Calling rest_get(f"repos/{REPO}/issues/{number}") with a falsy/empty number (URL becomes repos/{REPO}/issues/ which lists issues and returns a JSON array); REPO malformed so the path resolves to a list-returning endpoint; a transparent proxy or captive portal injecting a 200 JSON response that is not the issue object.
Common situations: REPO env var not in 'owner/repo' form; issue number passed as None or '' from an upstream parsing bug; corporate proxy mangling api.github.com responses; GitHub returning an unexpected-but-200 body during an incident.
Related errors
- No assignment event for {assignee} on issue #{issue_number}
- REST GET {path} failed after {retries} retries: {last_err}
- issue {number} has no created_at
- unknown benchmark '{benchmark_id}' (valid: {valid})
- unknown benchmark '{selector}' (valid: {valid})
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/23517543663f5ec0.
Report an issue: GitHub.