zed-industries/zed · error · RuntimeError
GraphQL failed after {retries} retries: {last_err}
Error message
GraphQL failed after {retries} retries: {last_err} What it means
graphql() exhausted all attempts — each one either hit a transient HTTP status (429/502/503/504) and slept, or raised a RequestException — and the final raise reports the retry count and last_err. When every attempt took the transient-status continue branch, last_err is still None and the message ends with 'retries: None', which itself is the tell that the failures were status-based, not exceptions.
Source
Thrown at script/triage_project_sync.py:227
if r.status_code == 200:
data = r.json()
if "errors" in data:
log(f"GraphQL errors: {json.dumps(data['errors'])[:400]}", "ERROR")
raise RuntimeError("GraphQL returned errors")
return data["data"]
if r.status_code in (429, 502, 503, 504):
wait = 2**attempt * 2
log(f"GraphQL {r.status_code}; retry in {wait}s", "WARN")
time.sleep(wait)
continue
log(f"GraphQL HTTP {r.status_code}: {r.text[:300]}", "ERROR")
r.raise_for_status()
except requests.RequestException as e:
last_err = e
wait = 2**attempt * 2
log(f"GraphQL threw {e}; retry in {wait}s", "WARN")
time.sleep(wait)
raise RuntimeError(f"GraphQL failed after {retries} retries: {last_err}")
# ---------------------------------------------------------------------------
# Issue data fetch
@dataclass
class IssueData:
number: int
node_id: str
title: str
state: str # "open" / "closed"
closed_at: datetime | None
created_at: datetime
reporter: str
assignees: list[str]
labels: list[str]
issue_type: str | None # e.g. "Bug", "Crash", "Meta", "Tracking", or NoneView on GitHub (pinned to bc538def45)
Solutions
- Check the WARN logs immediately above: they show the status or exception for every attempt
- On 429s: space out or batch the queries, and read the rateLimit cost field instead of guessing
- Increase retries/backoff for real outages, and abort runs fast when errors are non-transient
- If attempts were connection errors, verify runner egress/DNS and the API endpoint URL
Example fix
// before
raise RuntimeError(f"GraphQL failed after {retries} retries: {last_err}")
// after
if last_err is None:
last_err = RuntimeError("all attempts returned transient HTTP statuses (429/502/503/504)")
raise RuntimeError(f"GraphQL failed after {retries} retries: {last_err}") Defensive patterns
Strategy: retry
Validate before calling
def graphql_budget_available() -> bool:
data = graphql("query { rateLimit { remaining cost } }")
return data["rateLimit"]["remaining"] > data["rateLimit"]["cost"] Type guard
def is_transient_http_status(status: int) -> bool:
return status in (429, 502, 503, 504) Try / catch
try:
data = graphql(query, variables, retries=5)
except RuntimeError as exc:
if "retries: None" in str(exc):
raise SystemExit("All attempts hit transient HTTP statuses (429/5xx); back off and rerun later")
raise Prevention
- Check the rateLimit remaining/cost before batched runs
- Use exponential backoff with jitter on 429/5xx
- Initialize last_err in every branch that can exhaust the loop
- Monitor logs for repeated transient statuses as an early outage signal
When it happens
Trigger: Sustained 429 secondary rate limiting through all three attempts; a GitHub 5xx outage outlasting the backoff (2s, 4s); every attempt raising ConnectionError/Timeout because the runner's network or DNS is broken.
Common situations: Overlapping CI jobs exhausting the token's rate budget; GitHub incidents; sandboxed runners with blocked egress to api.github.com.
Related errors
- GraphQL errors: {json.dumps(data['errors'])[:300]}
- GraphQL error: {result['errors']}
- GraphQL error: {result['errors']}
- GraphQL error: {result['errors']}
- REST GET {path} failed after {retries} retries: {last_err}
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/e87f1c9cccd85358.
Report an issue: GitHub.