zed-industries/zed · error · ValueError

GraphQL errors: {json.dumps(data['errors'])[:300]}

Error message

GraphQL errors: {json.dumps(data['errors'])[:300]}

What it means

Raised when the GitHub GraphQL endpoint answers HTTP 200 but the response body contains an "errors" array; GitHub reports query-level failures (unresolvable ids, bad variables, missing scopes, rate-limit messages) in a 200 body, not as HTTP errors. The wrapper converts that into a ValueError carrying the first 300 characters of the serialized errors. Because the surrounding except only catches requests.RequestException, this ValueError escapes immediately and is never retried, even when the body error is transient.

Source

Thrown at script/github-check-new-issue-for-duplicates.py:104

    """Search issues, using GitHub's relevance ordering unless a sort is specified."""
    params = {"q": query, "per_page": per_page}
    if sort:
        params.update({"sort": sort, "order": "desc"})
    return github_api_get("/search/issues", params).get("items", [])


def github_api_graphql(query, variables=None):
    """Run a GraphQL query against the GitHub API, retrying transient failures. """
    url = f"{GITHUB_API}/graphql"
    for attempt in range(3):
        try:
            response = requests.post(
                url, headers=GITHUB_HEADERS, json={"query": query, "variables": variables or {}}
            )
            response.raise_for_status()
            data = response.json()
            if "errors" in data:
                raise ValueError(f"GraphQL errors: {json.dumps(data['errors'])[:300]}")
            return data["data"]
        except requests.RequestException as e:
            transient = isinstance(e, (requests.ConnectionError, requests.Timeout)) or (
                isinstance(e, requests.HTTPError) and e.response.status_code in TRANSIENT_HTTP_STATUSES
            )
            if not transient or attempt == 2:
                raise
            wait = 2 ** attempt
            log(f"  Transient GitHub GraphQL error ({e}); retrying in {wait}s")
            time.sleep(wait)


def check_team_membership(org, team_slug, username):
    """Check if user is an active member of a team."""
    try:
        data = github_api_get(f"/orgs/{org}/teams/{team_slug}/memberships/{username}")
        return data.get("state") == "active"
    except requests.HTTPError as e:

View on GitHub (pinned to bc538def45)

Solutions

  1. Log the full errors array (data['errors'][0]['message'] and ['type']) instead of the 300-char slice, then fix the exact query/variable/id it names
  2. Check the token: expiry date and scopes (repo, read:project) against the data being queried
  3. Reproduce with curl -H "Authorization: bearer $TOKEN" -d '{"query":"..."}' https://api.github.com/graphql to isolate the failing field
  4. If the error message indicates a rate limit, sleep and retry inside the loop instead of letting the ValueError escape (it currently bypasses the retry logic)

Example fix

// before
if "errors" in data:
    raise ValueError(f"GraphQL errors: {json.dumps(data['errors'])[:300]}")

// after
if "errors" in data:
    message = str(data['errors'][0].get('message', ''))
    if 'rate limit' in message.lower() and attempt < 2:
        wait = 2 ** attempt
        log(f"  Transient GraphQL body error; retrying in {wait}s")
        time.sleep(wait)
        continue
    raise ValueError(f"GraphQL errors: {json.dumps(data['errors'])[:300]}")
Defensive patterns

Strategy: retry

Validate before calling

def graphql_request_is_well_formed(query: str, variables: dict | None) -> bool:
    return bool(query and query.strip()) and (variables is None or isinstance(variables, dict))

Type guard

def is_graphql_error_body(payload: dict) -> bool:
    return isinstance(payload, dict) and isinstance(payload.get("errors"), list) and len(payload["errors"]) > 0

Try / catch

try:
    data = github_api_graphql(query, variables)
except ValueError as exc:
    # ValueError escapes the wrapper's RequestException handler, so classify here
    text = str(exc)
    if "rate limit" in text.lower():
        time.sleep(5)
        data = github_api_graphql(query, variables)
    else:
        raise

Prevention

When it happens

Trigger: POST to {GITHUB_API}/graphql returns 200 with errors, e.g. "Could not resolve to an Issue with the number of X" for a wrong/deleted issue number, INSUFFICIENT_SCOPES when GITHUB_TOKEN lacks repo/read:project, a malformed query or wrong variable type after a GitHub schema change, or a secondary rate-limit message embedded in the errors body.

Common situations: Expired or under-scoped CI token; querying an issue that was deleted or lives in another repo; GitHub GraphQL schema renames breaking a stored query; heavy CI runs hitting secondary rate limits that surface in the 200 body.

Related errors


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