{"record":{"id":"8a3ebf658e7fec29","repo":"zed-industries/zed","slug":"graphql-returned-errors","errorCode":null,"errorMessage":"GraphQL returned errors","messagePattern":"GraphQL returned errors","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"script/triage_project_sync.py","lineNumber":213,"sourceCode":"            break\n    return out\n\n\n# ---------------------------------------------------------------------------\n# GraphQL\n\n\ndef graphql(query: str, variables: dict | None = None, retries: int = 3) -> dict:\n    payload = {\"query\": query, \"variables\": variables or {}}\n    last_err: Exception | None = None\n    for attempt in range(retries):\n        try:\n            r = requests.post(GRAPHQL_API, headers=headers_graphql(), json=payload, timeout=30)\n            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","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/script/triage_project_sync.py#L195-L231","documentation":"graphql() treats any 200-with-errors body as fatal: it logs the error JSON (up to 400 chars) via log() and then raises this deliberately generic RuntimeError. The informative part lives in the log line, not the exception text; and unlike the HTTP 429/5xx path, body errors are never retried even when they are rate-limit messages.","triggerScenarios":"POST to GRAPHQL_API returns 200 with an errors array: malformed query or wrong variable types, unresolvable node ids (recreated projects/issues), missing project scope, or secondary rate-limit text delivered in the body.","commonSituations":"Schema drift after GitHub API changes; stale node ids after objects were recreated; token scope missing read:project; body-level rate limits under CI load that the HTTP retry path never sees.","solutions":["Match the exception to the preceding 'GraphQL errors:' log entry — that line holds the actual errors","Fix the named query/variable/permission issue","Put the errors into the exception text so failures are diagnosable from the traceback alone","Classify rate-limit-typed body errors and retry them like the HTTP 429 branch"],"exampleFix":"// before\nif \"errors\" in data:\n    log(f\"GraphQL errors: {json.dumps(data['errors'])[:400]}\", \"ERROR\")\n    raise RuntimeError(\"GraphQL returned errors\")\n\n// after\nif \"errors\" in data:\n    raise RuntimeError(f\"GraphQL returned errors: {json.dumps(data['errors'])[:400]}\")","handlingStrategy":"try-catch","validationCode":"def graphql_query_uses_known_fields(query: str, known_fields: set[str]) -> bool:\n    tokens = set(query.replace('{', ' ').replace('}', ' ').split())\n    return all(t in known_fields or not t.isalpha() for t in tokens)","typeGuard":"def is_graphql_error_body(payload: dict) -> bool:\n    return isinstance(payload, dict) and bool(payload.get(\"errors\"))","tryCatchPattern":"try:\n    data = graphql(query, variables)\nexcept RuntimeError as exc:\n    # The exception is generic; the logged line above it holds the real errors.\n    if str(exc) == \"GraphQL returned errors\":\n        raise SystemExit(\"Inspect the 'GraphQL errors:' log entry directly above this line\")\n    raise","preventionTips":["Put the error details into the exception message, not only the log","Treat body-level rate-limit errors as retryable","Re-validate queries when the GitHub schema changes","Structure logs so a failed run is diagnosable from artifacts alone"],"tags":["github-api","graphql","observability","python"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}