666ghj/MiroFish · error · StarHistoryError

GitHub GraphQL response had an unexpected shape

Error message

GitHub GraphQL response had an unexpected shape

What it means

Shape validation of the GraphQL data payload: either the nested field extraction (payload['data']['repository']['stargazers']['edges'/'pageInfo'], data['rateLimit']) raised KeyError/TypeError because a field is missing or a parent is null, or the subsequent all(isinstance(v, dict)) check found one of data/repository/stargazers/rate_limit/page_info is not a dict. A common legitimate cause is repository: null when owner/name point to a nonexistent or inaccessible repo, which makes the subscript chain fail.

Source

Thrown at scripts/star_history.py:254

            raise StarHistoryError(
                f"GitHub GraphQL request failed (exit {completed.returncode})"
            )
        try:
            payload = json.loads(completed.stdout)
        except json.JSONDecodeError as exc:
            raise StarHistoryError("GitHub GraphQL returned malformed JSON") from exc

        if not isinstance(payload, dict) or payload.get("errors"):
            raise StarHistoryError("GitHub GraphQL rejected the stargazer request")
        try:
            data = payload["data"]
            repository = data["repository"]
            stargazers = repository["stargazers"]
            rate_limit = data["rateLimit"]
            raw_edges = stargazers["edges"]
            page_info = stargazers["pageInfo"]
        except (KeyError, TypeError) as exc:
            raise StarHistoryError("GitHub GraphQL response had an unexpected shape") from exc

        if not all(
            isinstance(value, dict)
            for value in (data, repository, stargazers, rate_limit, page_info)
        ):
            raise StarHistoryError("GitHub GraphQL response had an unexpected shape")

        total_count = _strict_non_negative_int(
            stargazers.get("totalCount"), "GraphQL totalCount"
        )
        rate_remaining = _strict_non_negative_int(
            rate_limit.get("remaining"), "GraphQL rate remaining"
        )
        if not isinstance(raw_edges, list):
            raise StarHistoryError("GitHub GraphQL edges were not a list")

        edges: list[StargazerEdge] = []
        for raw_edge in raw_edges:

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Check the repo resolves: curl https://api.github.com/repos/666ghj/MiroFish — 404 or null means renamed/private; update REPOSITORY in scripts/star_history.py (and the fetch_star_count.py copy) accordingly.
  2. If data['repository'] is None specifically, it is the nonexistent/inaccessible case above; any other missing key means the pinned GRAPHQL_QUERY no longer matches the schema — diff it against GitHub's current stargazers type.
  3. For tests, build fixtures from a captured real response rather than hand-writing the nested structure.

Example fix

# before: null repository surfaces as opaque shape error
data = payload["data"]
repository = data["repository"]
stargazers = repository["stargazers"]  # TypeError: 'NoneType' ...

# after: name the real cause before subscripting
repository = (payload.get("data") or {}).get("repository")
if repository is None:
    raise StarHistoryError(
        f"GitHub GraphQL returned no repository for "
        f"{REPOSITORY_OWNER}/{REPOSITORY_NAME} (renamed, private, or gone)"
    )
Defensive patterns

Strategy: type-guard

Type guard

from typing import Any

def is_stargazer_page_payload(payload: Any) -> bool:
    """Full expected shape: data.repository.stargazers{edges,pageInfo} + rateLimit."""
    try:
        data = payload["data"]
        stargazers = data["repository"]["stargazers"]
        return all(
            isinstance(node, dict)
            for node in (data, data["repository"], stargazers,
                         data["rateLimit"], stargazers["pageInfo"])
        ) and isinstance(stargazers.get("edges"), list)
    except (KeyError, TypeError):
        return False

Try / catch

try:
    page = gateway.fetch_stargazer_page(after)
except StarHistoryError as exc:
    if "unexpected shape" in str(exc):
        repository = (payload.get("data") or {}).get("repository")
        if repository is None:
            raise SystemExit(
                "repo 666ghj/MiroFish not visible: renamed, private, "
                "or token lost access — update REPOSITORY constants"
            )
        raise SystemExit("GraphQL schema drift; update GRAPHQL_QUERY")
    raise

Prevention

When it happens

Trigger: REPOSITORY_OWNER/REPOSITORY_NAME constants pointing at a renamed/deleted/private repo (repository null); GraphQL partial data where rateLimit is omitted under errors; pageInfo absent on the last page's shape change; test fixtures missing edges or pageInfo keys.

Common situations: Repository 666ghj/MiroFish renamed or made private after the constants were pinned; token losing access mid-backfill; GitHub returning partial data during an incident; hand-written test payloads missing nested keys.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/837c89fb4458722b. Report an issue: GitHub.