{"record":{"id":"ba2ae3096c4e373b","repo":"666ghj/MiroFish","slug":"github-api-response-had-an-unexpected-shape","errorCode":null,"errorMessage":"GitHub API response had an unexpected shape","messagePattern":"GitHub API response had an unexpected shape","errorType":"exception","errorClass":"FetchError","httpStatus":null,"severity":"error","filePath":"scripts/fetch_star_count.py","lineNumber":118,"sourceCode":"            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:\n        print(f\"error: {exc}\", file=sys.stderr)\n        return 1","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/fetch_star_count.py#L100-L136","documentation":"After successful JSON parsing, the document must be a JSON object (Python dict). This error means the body parsed fine but was an array, string, number, boolean, or null — GitHub's /repos endpoint always returns an object, so a non-dict indicates the URL now serves a different resource or something rewrote the response.","triggerScenarios":"API_URL pointing at a collection endpoint that returns a JSON array; a proxy returning a JSON-encoded status object from a different service; test doubles returning json.dumps([1,2,3]).","commonSituations":"REPOSITORY constant edited to include a query string or path segment that changes the response shape; mock servers returning list payloads; gateway routing api.github.com to an internal service.","solutions":["Confirm API_URL is exactly https://api.github.com/repos/{owner}/{repo} with no suffix.","Inspect the parsed type in a debug run: print(type(json.loads(payload))) and compare against a direct curl of the same URL.","Fix test fixtures to return an object literal, not a list."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"import json\nfrom typing import Any\n\ndef is_repo_document(document: Any) -> bool:\n    \"\"\"Narrow parsed JSON to the expected GitHub repository object.\"\"\"\n    return (\n        isinstance(document, dict)\n        and isinstance(document.get(\"stargazers_count\"), int)\n    )","tryCatchPattern":"try:\n    count = fetch_star_count(token)\nexcept FetchError as exc:\n    if exc.args[0] == \"GitHub API response had an unexpected shape\":\n        # parsed fine but not an object: URL drift or gateway rewrite\n        raise SystemExit(\"expected a JSON object from /repos; verify API_URL\")\n    raise","preventionTips":["Pin API_URL construction to a single constant so it cannot drift toward list endpoints.","Validate document shape before field access in your own integrations — dict-ness is the cheapest contract check.","Test doubles should mirror the real object literal, including snake_case keys."],"tags":["json","api-contract","fetch-star-count"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}