666ghj/MiroFish · error · StarHistoryError
GitHub returned an invalid pagination cursor
Error message
GitHub returned an invalid pagination cursor
What it means
Defensive validation of the GraphQL pagination cursor before it is injected into the gh -f after= argument: the cursor must be non-empty and contain no \n or \r. The newline ban keeps the subprocess argument clean (no argument smuggling) and rejects corrupted cursor strings. The cursor normally comes from a previous response's pageInfo.endCursor, so failure means GitHub returned a garbage cursor or persisted state was corrupted.
Source
Thrown at scripts/star_history.py:231
def __init__(self, runner: CommandRunner | None = None) -> None:
self._runner = runner or SubprocessCommandRunner()
def fetch_stargazer_page(self, after: str | None) -> StargazerPage:
arguments = [
"gh",
"api",
"graphql",
"-f",
f"query={GRAPHQL_QUERY}",
"-f",
f"owner={REPOSITORY_OWNER}",
"-f",
f"name={REPOSITORY_NAME}",
]
if after is not None:
if not after or "\n" in after or "\r" in after:
raise StarHistoryError("GitHub returned an invalid pagination cursor")
arguments.extend(("-f", f"after={after}"))
completed = self._runner.run(arguments)
if completed.returncode != 0:
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"]View on GitHub (pinned to b5b53acc57)
Solutions
- Inspect the cursor value at the failure point (log it repr()'d) — empty string usually means pagination should have stopped, so verify the loop's hasNextPage handling.
- If resuming from corrupted state, remove the bad cursor from the persisted history and re-run the backfill page fetch from the last known-good cursor.
- Never hand-edit cursors in history.json; treat them as opaque tokens exactly as the docstring requires.
Defensive patterns
Strategy: validation
Validate before calling
def is_usable_cursor(cursor: object) -> bool:
"""Opaque GraphQL cursor: non-empty string, single line."""
return (
isinstance(cursor, str)
and cursor != ""
and "\n" not in cursor
and "\r" not in cursor
)
# before passing a cursor read from persisted state
assert is_usable_cursor(after), f"corrupted cursor in history: {after!r}" Try / catch
try:
page = gateway.fetch_stargazer_page(after)
except StarHistoryError as exc:
if "invalid pagination cursor" in str(exc):
# discard the suspect cursor and restart pagination from None
page = gateway.fetch_stargazer_page(None)
else:
raise Prevention
- Treat cursors as opaque — never hand-edit or reformat them in history.json.
- Validate persisted cursors with the same single-line/non-empty rule before resuming.
- Cross-check hasNextPage before using endCursor; empty endCursor with hasNextPage true is the usual corruption signal.
When it happens
Trigger: GitHub's pageInfo.endCursor comes back as an empty string (can happen when hasNextPage is erroneously true); the local history state file was hand-edited so a stored cursor is empty or contains a newline; a GraphQL response shape change puts a non-string into the cursor chain.
Common situations: Resuming a backfill from a manually patched history.json; truncated cursor copied by hand; upstream API anomaly during pagination.
Related errors
- GitHub GraphQL returned an invalid edge cursor
- GitHub GraphQL returned invalid page information
- GitHub GraphQL returned an invalid page cursor
- GitHub GraphQL omitted the next page cursor
- GitHub GraphQL rejected the stargazer request
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/0821bb18706f166d.
Report an issue: GitHub.