{"record":{"id":"e87f1c9cccd85358","repo":"zed-industries/zed","slug":"graphql-failed-after-retries-retries-last-err","errorCode":null,"errorMessage":"GraphQL failed after {retries} retries: {last_err}","messagePattern":"GraphQL failed after (.+?) retries: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"script/triage_project_sync.py","lineNumber":227,"sourceCode":"            if r.status_code == 200:\n                data = r.json()\n                if \"errors\" in data:\n                    log(f\"GraphQL errors: {json.dumps(data['errors'])[:400]}\", \"ERROR\")\n                    raise RuntimeError(\"GraphQL returned errors\")\n                return data[\"data\"]\n            if r.status_code in (429, 502, 503, 504):\n                wait = 2**attempt * 2\n                log(f\"GraphQL {r.status_code}; retry in {wait}s\", \"WARN\")\n                time.sleep(wait)\n                continue\n            log(f\"GraphQL HTTP {r.status_code}: {r.text[:300]}\", \"ERROR\")\n            r.raise_for_status()\n        except requests.RequestException as e:\n            last_err = e\n            wait = 2**attempt * 2\n            log(f\"GraphQL threw {e}; retry in {wait}s\", \"WARN\")\n            time.sleep(wait)\n    raise RuntimeError(f\"GraphQL failed after {retries} retries: {last_err}\")\n\n\n# ---------------------------------------------------------------------------\n# Issue data fetch\n\n\n@dataclass\nclass IssueData:\n    number: int\n    node_id: str\n    title: str\n    state: str  # \"open\" / \"closed\"\n    closed_at: datetime | None\n    created_at: datetime\n    reporter: str\n    assignees: list[str]\n    labels: list[str]\n    issue_type: str | None  # e.g. \"Bug\", \"Crash\", \"Meta\", \"Tracking\", or None","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/script/triage_project_sync.py#L209-L245","documentation":"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.","triggerScenarios":"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.","commonSituations":"Overlapping CI jobs exhausting the token's rate budget; GitHub incidents; sandboxed runners with blocked egress to api.github.com.","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"],"exampleFix":"// before\nraise RuntimeError(f\"GraphQL failed after {retries} retries: {last_err}\")\n\n// after\nif last_err is None:\n    last_err = RuntimeError(\"all attempts returned transient HTTP statuses (429/502/503/504)\")\nraise RuntimeError(f\"GraphQL failed after {retries} retries: {last_err}\")","handlingStrategy":"retry","validationCode":"def graphql_budget_available() -> bool:\n    data = graphql(\"query { rateLimit { remaining cost } }\")\n    return data[\"rateLimit\"][\"remaining\"] > data[\"rateLimit\"][\"cost\"]","typeGuard":"def is_transient_http_status(status: int) -> bool:\n    return status in (429, 502, 503, 504)","tryCatchPattern":"try:\n    data = graphql(query, variables, retries=5)\nexcept RuntimeError as exc:\n    if \"retries: None\" in str(exc):\n        raise SystemExit(\"All attempts hit transient HTTP statuses (429/5xx); back off and rerun later\")\n    raise","preventionTips":["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"],"tags":["github-api","graphql","retry","rate-limit","network","python"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}