666ghj/MiroFish · error · StarHistoryError

GitHub GraphQL returned malformed JSON

Error message

GitHub GraphQL returned malformed JSON

What it means

Raised when gh exits 0 but its stdout is not parseable JSON (json.JSONDecodeError). gh api normally prints the raw JSON response, so invalid stdout means gh printed human-readable text (e.g. a notice, a prompt, or spinner output leaked), or the runner's environment mangled the output.

Source

Thrown at scripts/star_history.py:242

            "-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"]
            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")

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Run the exact gh command manually and inspect what precedes/follows the JSON on stdout.
  2. Neutralize gh's environment for the subprocess: pass env with GH_FORCE_TTY unset, NO_COLOR=1, GH_NO_UPDATE_NOTIFIER=1, and ensure no pager.
  3. In tests, make the fake runner return json.dumps({...}) exactly.

Example fix

# before
completed = subprocess.run(args, capture_output=True, text=True, timeout=45)

# after: run gh in a pristine, non-interactive environment
env = {
    **{
        k: v for k, v in os.environ.items()
        if k in ("PATH", "HOME") or k.startswith("GH_TOKEN")
    },
    "NO_COLOR": "1",
    "GH_NO_UPDATE_NOTIFIER": "1",
    "GH_CONFIG_DIR": os.environ.get("GH_CONFIG_DIR", ""),
}
completed = subprocess.run(args, capture_output=True, text=True, timeout=45, env=env or None)
Defensive patterns

Strategy: validation

Validate before calling

import json

# validate the runner output before the gateway parses it (test doubles)
def is_json_object(stdout: str) -> bool:
    try:
        return isinstance(json.loads(stdout), dict)
    except (json.JSONDecodeError, ValueError):
        return False

Try / catch

try:
    page = gateway.fetch_stargazer_page(after)
except StarHistoryError as exc:
    if "malformed JSON" in str(exc):
        # gh printed non-JSON: rerun manually with the same args to see stdout
        raise SystemExit(
            "gh emitted non-JSON stdout; check GH_FORCE_TTY, pagers, "
            "and gh update notices"
        )
    raise

Prevention

When it happens

Trigger: gh emitting a banner/upgrade notice before the JSON on stdout; gh configured with output formatting (GH_FORCE_TTY, pager, or gh config format settings) that wraps the JSON; locale/encoding issues in subprocess text mode; a mocked CommandRunner returning non-JSON stdout in tests.

Common situations: First-run gh notices; CI environments with GH_FORCE_TTY=1; users who set `gh config set format table` style defaults; test doubles returning ''.

Understand the failure class

Related errors


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