{"record":{"id":"d74854e011c883c1","repo":"666ghj/MiroFish","slug":"github-graphql-returned-malformed-json","errorCode":null,"errorMessage":"GitHub GraphQL returned malformed JSON","messagePattern":"GitHub GraphQL returned malformed JSON","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":242,"sourceCode":"            \"-f\",\n            f\"owner={REPOSITORY_OWNER}\",\n            \"-f\",\n            f\"name={REPOSITORY_NAME}\",\n        ]\n        if after is not None:\n            if not after or \"\\n\" in after or \"\\r\" in after:\n                raise StarHistoryError(\"GitHub returned an invalid pagination cursor\")\n            arguments.extend((\"-f\", f\"after={after}\"))\n\n        completed = self._runner.run(arguments)\n        if completed.returncode != 0:\n            raise StarHistoryError(\n                f\"GitHub GraphQL request failed (exit {completed.returncode})\"\n            )\n        try:\n            payload = json.loads(completed.stdout)\n        except json.JSONDecodeError as exc:\n            raise StarHistoryError(\"GitHub GraphQL returned malformed JSON\") from exc\n\n        if not isinstance(payload, dict) or payload.get(\"errors\"):\n            raise StarHistoryError(\"GitHub GraphQL rejected the stargazer request\")\n        try:\n            data = payload[\"data\"]\n            repository = data[\"repository\"]\n            stargazers = repository[\"stargazers\"]\n            rate_limit = data[\"rateLimit\"]\n            raw_edges = stargazers[\"edges\"]\n            page_info = stargazers[\"pageInfo\"]\n        except (KeyError, TypeError) as exc:\n            raise StarHistoryError(\"GitHub GraphQL response had an unexpected shape\") from exc\n\n        if not all(\n            isinstance(value, dict)\n            for value in (data, repository, stargazers, rate_limit, page_info)\n        ):\n            raise StarHistoryError(\"GitHub GraphQL response had an unexpected shape\")","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L224-L260","documentation":"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.","triggerScenarios":"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.","commonSituations":"First-run gh notices; CI environments with GH_FORCE_TTY=1; users who set `gh config set format table` style defaults; test doubles returning ''.","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."],"exampleFix":"# before\ncompleted = subprocess.run(args, capture_output=True, text=True, timeout=45)\n\n# after: run gh in a pristine, non-interactive environment\nenv = {\n    **{\n        k: v for k, v in os.environ.items()\n        if k in (\"PATH\", \"HOME\") or k.startswith(\"GH_TOKEN\")\n    },\n    \"NO_COLOR\": \"1\",\n    \"GH_NO_UPDATE_NOTIFIER\": \"1\",\n    \"GH_CONFIG_DIR\": os.environ.get(\"GH_CONFIG_DIR\", \"\"),\n}\ncompleted = subprocess.run(args, capture_output=True, text=True, timeout=45, env=env or None)","handlingStrategy":"validation","validationCode":"import json\n\n# validate the runner output before the gateway parses it (test doubles)\ndef is_json_object(stdout: str) -> bool:\n    try:\n        return isinstance(json.loads(stdout), dict)\n    except (json.JSONDecodeError, ValueError):\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    page = gateway.fetch_stargazer_page(after)\nexcept StarHistoryError as exc:\n    if \"malformed JSON\" in str(exc):\n        # gh printed non-JSON: rerun manually with the same args to see stdout\n        raise SystemExit(\n            \"gh emitted non-JSON stdout; check GH_FORCE_TTY, pagers, \"\n            \"and gh update notices\"\n        )\n    raise","preventionTips":["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."],"tags":["json","github-cli","subprocess","star-history"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}