666ghj/MiroFish · error · FetchError
GitHub API returned malformed JSON
Error message
GitHub API returned malformed JSON
What it means
Raised when json.loads(payload) fails with UnicodeDecodeError, JSONDecodeError, or ValueError — the 200-status body from api.github.com is not valid JSON. Since the status was 200 and the size checks passed, this means the wrong content came back: an HTML error page, a proxy block page, or a truncated body.
Source
Thrown at scripts/fetch_star_count.py:116
try:
with response:
if response.geturl() != API_URL:
raise FetchError("GitHub API redirect was refused")
status = response.getcode()
if status != 200:
raise _status_error(status)
payload = _read_response(response)
except FetchError:
raise
except (TimeoutError, OSError):
raise FetchError("GitHub API response could not be read") from None
except Exception:
raise FetchError("GitHub API response could not be processed") from None
try:
document = json.loads(payload)
except (UnicodeDecodeError, json.JSONDecodeError, ValueError):
raise FetchError("GitHub API returned malformed JSON") from None
if not isinstance(document, dict):
raise FetchError("GitHub API response had an unexpected shape")
count = document.get("stargazers_count")
if type(count) is not int or count < 0:
raise FetchError("GitHub API returned an invalid stargazers_count")
return count
def main(argv: list[str] | None = None) -> int:
arguments = sys.argv[1:] if argv is None else argv
if arguments:
print("error: this command accepts no arguments", file=sys.stderr)
return 2
try:
count = fetch_star_count(os.environ.get("GITHUB_TOKEN", ""))
except FetchError as exc:View on GitHub (pinned to b5b53acc57)
Solutions
- Reproduce with curl and inspect the content: curl -s -H "Authorization: Bearer $TOKEN" https://api.github.com/repos/666ghj/MiroFish | head -c 400 — HTML output confirms interception.
- Bypass the proxy/captive network for api.github.com or fix its exclusion list.
- In tests, make the fake payload a real JSON object: json.dumps({'stargazers_count': 42}).encode().
Defensive patterns
Strategy: try-catch
Type guard
def looks_like_github_repo_json(payload: bytes) -> bool:
"""Cheap pre-parse sanity: GitHub repo JSON starts with an object."""
stripped = payload.lstrip()
return stripped.startswith(b"{") and b"stargazers_count" in stripped Try / catch
try:
count = fetch_star_count(token)
except FetchError as exc:
if exc.args[0] == "GitHub API returned malformed JSON":
# status was 200 but body wasn't JSON -> interception, not GitHub
raise SystemExit("non-JSON 200 from api.github.com; check proxy/captive portal")
raise Prevention
- A 200 with HTML means interception — verify with curl from the same host before touching code.
- Keep test payloads as json.dumps({...}).encode(), never hand-typed strings.
- Add a canary fetch (api.github.com/rate_limit) in CI to detect captive networks before the scheduled run.
When it happens
Trigger: Transparent proxy or captive portal returning HTML with status 200; response truncated between header and body (proxy content mismatch); a custom opener returning fixture bytes that are not JSON.
Common situations: Hotel/airport captive portals; corporate proxies rewriting responses; GitHub API served by a cached error page; test openers returning empty strings.
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 API returned invalid response metadata
- GitHub API response exceeded the size limit
- GitHub API response had an unexpected shape
- GitHub API returned an invalid stargazers_count
- Ontology result must be an object
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/6522f9f7a8c80d47.
Report an issue: GitHub.