{"record":{"id":"6522f9f7a8c80d47","repo":"666ghj/MiroFish","slug":"github-api-returned-malformed-json","errorCode":null,"errorMessage":"GitHub API returned malformed JSON","messagePattern":"GitHub API returned malformed JSON","errorType":"exception","errorClass":"FetchError","httpStatus":null,"severity":"error","filePath":"scripts/fetch_star_count.py","lineNumber":116,"sourceCode":"    try:\n        with response:\n            if response.geturl() != API_URL:\n                raise FetchError(\"GitHub API redirect was refused\")\n            status = response.getcode()\n            if status != 200:\n                raise _status_error(status)\n            payload = _read_response(response)\n    except FetchError:\n        raise\n    except (TimeoutError, OSError):\n        raise FetchError(\"GitHub API response could not be read\") from None\n    except Exception:\n        raise FetchError(\"GitHub API response could not be processed\") from None\n\n    try:\n        document = json.loads(payload)\n    except (UnicodeDecodeError, json.JSONDecodeError, ValueError):\n        raise FetchError(\"GitHub API returned malformed JSON\") from None\n    if not isinstance(document, dict):\n        raise FetchError(\"GitHub API response had an unexpected shape\")\n\n    count = document.get(\"stargazers_count\")\n    if type(count) is not int or count < 0:\n        raise FetchError(\"GitHub API returned an invalid stargazers_count\")\n    return count\n\n\ndef main(argv: list[str] | None = None) -> int:\n    arguments = sys.argv[1:] if argv is None else argv\n    if arguments:\n        print(\"error: this command accepts no arguments\", file=sys.stderr)\n        return 2\n\n    try:\n        count = fetch_star_count(os.environ.get(\"GITHUB_TOKEN\", \"\"))\n    except FetchError as exc:","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/fetch_star_count.py#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hotel/airport captive portals; corporate proxies rewriting responses; GitHub API served by a cached error page; test openers returning empty strings.","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()."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def looks_like_github_repo_json(payload: bytes) -> bool:\n    \"\"\"Cheap pre-parse sanity: GitHub repo JSON starts with an object.\"\"\"\n    stripped = payload.lstrip()\n    return stripped.startswith(b\"{\") and b\"stargazers_count\" in stripped","tryCatchPattern":"try:\n    count = fetch_star_count(token)\nexcept FetchError as exc:\n    if exc.args[0] == \"GitHub API returned malformed JSON\":\n        # status was 200 but body wasn't JSON -> interception, not GitHub\n        raise SystemExit(\"non-JSON 200 from api.github.com; check proxy/captive portal\")\n    raise","preventionTips":["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."],"tags":["json","proxy","captive-portal","fetch-star-count"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}