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
- Run the exact gh command manually and inspect what precedes/follows the JSON on stdout.
- 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.
- 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
- Invoke gh with NO_COLOR=1, GH_NO_UPDATE_NOTIFIER=1, and no TTY/pager in the subprocess env.
- Keep test CommandRunner doubles returning exact json.dumps strings.
- Capture stdout of a manual gh run once and store it as the CI fixture so environment drift is caught early.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- GitHub CLI (gh) is required for backfill
- GitHub GraphQL request timed out
- GitHub GraphQL request failed (exit {completed.returncode})
- history state must be a JSON object
- Ontology result must be an object
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/d74854e011c883c1.
Report an issue: GitHub.