zed-industries/zed · error · RuntimeError

GraphQL returned errors

Error message

GraphQL returned errors

What it means

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.

Source

Thrown at script/triage_project_sync.py:213

            break
    return out


# ---------------------------------------------------------------------------
# GraphQL


def graphql(query: str, variables: dict | None = None, retries: int = 3) -> dict:
    payload = {"query": query, "variables": variables or {}}
    last_err: Exception | None = None
    for attempt in range(retries):
        try:
            r = requests.post(GRAPHQL_API, headers=headers_graphql(), json=payload, timeout=30)
            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

View on GitHub (pinned to bc538def45)

Solutions

  1. Match the exception to the preceding 'GraphQL errors:' log entry — that line holds the actual errors
  2. Fix the named query/variable/permission issue
  3. Put the errors into the exception text so failures are diagnosable from the traceback alone
  4. Classify rate-limit-typed body errors and retry them like the HTTP 429 branch

Example fix

// before
if "errors" in data:
    log(f"GraphQL errors: {json.dumps(data['errors'])[:400]}", "ERROR")
    raise RuntimeError("GraphQL returned errors")

// after
if "errors" in data:
    raise RuntimeError(f"GraphQL returned errors: {json.dumps(data['errors'])[:400]}")
Defensive patterns

Strategy: try-catch

Validate before calling

def graphql_query_uses_known_fields(query: str, known_fields: set[str]) -> bool:
    tokens = set(query.replace('{', ' ').replace('}', ' ').split())
    return all(t in known_fields or not t.isalpha() for t in tokens)

Type guard

def is_graphql_error_body(payload: dict) -> bool:
    return isinstance(payload, dict) and bool(payload.get("errors"))

Try / catch

try:
    data = graphql(query, variables)
except RuntimeError as exc:
    # The exception is generic; the logged line above it holds the real errors.
    if str(exc) == "GraphQL returned errors":
        raise SystemExit("Inspect the 'GraphQL errors:' log entry directly above this line")
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/8a3ebf658e7fec29. Report an issue: GitHub.