zed-industries/zed · error · RuntimeError

GraphQL errors: {data['errors']}

Error message

GraphQL errors: {data['errors']}

What it means

This helper posts a GraphQL query once (no HTTP retry wrapper) and raises when the 200 body contains "errors", unless partial_errors_ok is set and the body still carries usable data — in that tolerated case it prints the errors and returns the partial data. So the raise means either the call was strict, or partial mode was requested but the failure was total (no data key, e.g. data: null).

Source

Thrown at script/github-track-duplicate-bot-effectiveness.py:170


def parse_suggested_issues(comment_body):
    """Extract issue numbers from the bot's comment (lines like '- #12345')."""
    return [int(match) for match in re.findall(r"^- #(\d+)", comment_body, re.MULTILINE)]


def github_api_graphql(query, variables=None, partial_errors_ok=False):
    """Execute a GitHub GraphQL query. Raises on errors unless partial_errors_ok is set."""
    response = requests.post(
        GRAPHQL_URL,
        headers=GITHUB_HEADERS,
        json={"query": query, "variables": variables or {}},
    )
    response.raise_for_status()
    data = response.json()
    if "errors" in data:
        if not partial_errors_ok or "data" not in data:
            raise RuntimeError(f"GraphQL errors: {data['errors']}")
        print(f"  GraphQL partial errors (ignored): {data['errors']}")
    return data["data"]


def find_canonical_among(duplicate_number, candidates):
    """Check if any candidate issue has duplicate_number marked as a duplicate.

    The MarkedAsDuplicateEvent lives on the canonical issue's timeline, not the
    duplicate's. So to find which canonical issue our duplicate was closed against,
    we check each candidate's timeline for a MarkedAsDuplicateEvent whose
    `duplicate` field matches our issue.

    Returns the matching canonical issue number, or None.
    """
    if not candidates:
        return None

    # candidate issue numbers are baked into the query body via field aliases

View on GitHub (pinned to bc538def45)

Solutions

  1. Inspect data['errors'] in the message: it names the node and failure type
  2. Pass partial_errors_ok=True for fan-out queries where individual nulls are acceptable, and null-check each node before use
  3. Filter out deleted/inaccessible candidates before querying their timelines
  4. Wrap the single POST with the same transient-status retry the other scripts use

Example fix

// before
data = github_api_graphql(query, variables)
nodes = data["node"]["timelineItems"]["nodes"]

// after
data = github_api_graphql(query, variables, partial_errors_ok=True) or {}
nodes = ((data.get("node") or {}).get("timelineItems") or {}).get("nodes") or []
Defensive patterns

Strategy: fallback

Validate before calling

def candidates_are_queryable(candidates: list) -> bool:
    return all(c.get("number") and c.get("node_id") for c in candidates)

Type guard

def graphql_response_has_data(payload: dict) -> bool:
    return isinstance(payload.get("data"), dict)

Try / catch

try:
    data = github_api_graphql(query, variables, partial_errors_ok=True)
except RuntimeError:
    data = {}  # tolerate total failure of one candidate; skip it downstream
nodes = ((data.get("node") or {}).get("timelineItems") or {}).get("nodes") or []

Prevention

When it happens

Trigger: Fan-out timeline queries (find_canonical_among) hit issues that error individually; with partial_errors_ok=False any body error raises; with partial_errors_ok=True a wholly failed query (errors plus data:null, e.g. querying a deleted issue node) still raises because there is nothing to return.

Common situations: Candidate issues deleted or made private mid-run; token lacking scope for MarkedAsDuplicateEvent timeline items; callers forgetting to pass partial_errors_ok on queries designed to tolerate nulls.

Related errors


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