666ghj/MiroFish · error · StarHistoryError

GitHub GraphQL returned an invalid page cursor

Error message

GitHub GraphQL returned an invalid page cursor

What it means

pageInfo.endCursor was present but not a string (None is allowed and handled separately — the failure is for numbers, dicts, lists, bools). endCursor is the opaque pagination token for the next request, so the parser raises StarHistoryError('GitHub GraphQL returned an invalid page cursor') when it cannot be used as one.

Source

Thrown at scripts/star_history.py:288

        edges: list[StargazerEdge] = []
        for raw_edge in raw_edges:
            if not isinstance(raw_edge, dict):
                raise StarHistoryError("GitHub GraphQL returned an invalid edge")
            cursor = raw_edge.get("cursor")
            starred_at = raw_edge.get("starredAt")
            if not isinstance(cursor, str) or not cursor:
                raise StarHistoryError("GitHub GraphQL returned an invalid edge cursor")
            if not isinstance(starred_at, str):
                raise StarHistoryError("GitHub GraphQL returned an invalid star timestamp")
            edges.append(StargazerEdge(cursor, _parse_github_timestamp(starred_at)))

        has_next_page = page_info.get("hasNextPage")
        end_cursor = page_info.get("endCursor")
        if type(has_next_page) is not bool:
            raise StarHistoryError("GitHub GraphQL returned invalid page information")
        if end_cursor is not None and not isinstance(end_cursor, str):
            raise StarHistoryError("GitHub GraphQL returned an invalid page cursor")
        if has_next_page and not end_cursor:
            raise StarHistoryError("GitHub GraphQL omitted the next page cursor")

        return StargazerPage(
            total_count=total_count,
            edges=tuple(edges),
            has_next_page=has_next_page,
            end_cursor=end_cursor,
            rate_remaining=rate_remaining,
        )


def _strict_non_negative_int(value: Any, label: str) -> int:
    if type(value) is not int or value < 0:
        raise StarHistoryError(f"{label} must be a non-negative integer")
    return value

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Fix the fixture/mock to return endCursor as a string or null
  2. Log the raw end_cursor value to identify its actual type
  3. Re-run against the real GitHub endpoint to confirm it is a stub-only issue

Example fix

// before (fixture)
"endCursor": 42

// after (fixture)
"endCursor": "42"
Defensive patterns

Strategy: validation

Validate before calling

end_cursor = page_info.get("endCursor", "missing")
if end_cursor != "missing" and end_cursor is not None and not isinstance(end_cursor, str):
    raise StarHistoryError("endCursor is not a string")

Type guard

def end_cursor_ok(value: object) -> bool:
    return value is None or isinstance(value, str)

Try / catch

try:
    page = _parse_graphql_page(payload)
except StarHistoryError as exc:
    if "invalid page cursor" in str(exc):
        raise StarHistoryError("pagination token unusable; refusing to continue") from exc
    raise

Prevention

When it happens

Trigger: endCursor: 12345, an object, or a list in the response — typically from a fixture or a modified/stubbed GraphQL endpoint, since real GitHub always returns a string or null.

Common situations: Hand-built fixtures where endCursor was an integer cursor index; a mock server returning {'endCursor': {'value': 'x'}}.

Related errors


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