{"record":{"id":"7037d01d109bbac4","repo":"can1357/oh-my-pi","slug":"proxy-returned-malformed-repo-payload","errorCode":null,"errorMessage":"proxy returned malformed repo payload","messagePattern":"proxy returned malformed repo payload","errorType":"exception","errorClass":"GitHubError","httpStatus":500,"severity":"error","filePath":"python/robomp/src/proxy_client.py","lineNumber":596,"sourceCode":"\n\ndef _release_from(data: Any) -> ReleaseInfo:\n    if not isinstance(data, dict):\n        raise GitHubError(500, \"proxy returned malformed release payload\")\n    name = data.get(\"name\")\n    return ReleaseInfo(\n        tag=str(data.get(\"tag\") or \"\"),\n        name=str(name) if name is not None else None,\n        draft=bool(data.get(\"draft\")),\n        prerelease=bool(data.get(\"prerelease\")),\n        html_url=str(data.get(\"html_url\") or \"\"),\n        asset_names=tuple(str(asset) for asset in data.get(\"asset_names\") or []),\n    )\n\n\ndef _repo_from(data: Any) -> RepoInfo:\n    if not isinstance(data, dict):\n        raise GitHubError(500, \"proxy returned malformed repo payload\")\n    return RepoInfo(\n        full_name=str(data[\"full_name\"]),\n        default_branch=str(data[\"default_branch\"]),\n        clone_url=str(data[\"clone_url\"]),\n        private=bool(data.get(\"private\", False)),\n    )\n\n\ndef _issue_from(data: Any) -> IssueInfo:\n    if not isinstance(data, dict):\n        raise GitHubError(500, \"proxy returned malformed issue payload\")\n    labels = data.get(\"labels\") or []\n    return IssueInfo(\n        repo=str(data[\"repo\"]),\n        number=int(data[\"number\"]),\n        title=str(data.get(\"title\") or \"\"),\n        body=str(data.get(\"body\") or \"\"),\n        state=str(data.get(\"state\") or \"open\"),","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy_client.py#L578-L614","documentation":"`_repo_from` builds a `RepoInfo` from the gh-proxy's repo payload and requires it to be a JSON object; otherwise it raises `GitHubError(500, ...)`. The subsequent code also accesses `full_name`, `default_branch`, and `clone_url` unconditionally, so the object check is the guard that produces this clear error instead of a KeyError downstream.","triggerScenarios":"`get_repo` receiving a non-dict repo payload from the proxy — version skew between proxy and client, an error string/array/body returned instead of the repo object, or a custom proxy emitting a different repo shape.","commonSituations":"Proxy misdeployment or old proxy binary behind a new client, authentication gateways returning JSON error objects wrapped differently, network appliances substituting bodies, forked proxies.","solutions":["Log the raw proxy response for get_repo and compare with the expected RepoInfo fields","Upgrade/align the proxy server and robomp client versions","Check for intermediaries replacing the response body and test the proxy endpoint directly","If you control the proxy, ensure the repo response always includes full_name, default_branch, and clone_url"],"exampleFix":"// before\ninfo = client.get_repo(\"owner/name\")\n// after\ntry:\n    info = client.get_repo(\"owner/name\")\nexcept GitHubError as e:\n    log.error(\"repo payload malformed\", extra={\"repo\": \"owner/name\", \"detail\": str(e)})\n    raise","handlingStrategy":"type-guard","validationCode":"repo_payload = fetch_raw_repo(name)\nif not isinstance(repo_payload, dict):\n    raise ValueError(f\"proxy repo payload is {type(repo_payload).__name__}, expected object\")\nmissing = {\"full_name\", \"default_branch\", \"clone_url\"} - repo_payload.keys()\nif missing:\n    raise ValueError(f\"proxy repo payload missing fields: {sorted(missing)}\")","typeGuard":"def is_repo_payload(v: object) -> TypeGuard[dict]:\n    return isinstance(v, dict) and {\"full_name\", \"default_branch\", \"clone_url\"} <= v.keys()","tryCatchPattern":"try:\n    info = client.get_repo(name)\nexcept GitHubError as e:\n    if \"malformed repo payload\" in str(e):\n        log.error(\"proxy repo schema mismatch\", extra={\"repo\": name})\n        raise ConnectionError(f\"gh-proxy at {proxy_url} returned incompatible repo payload\") from e\n    raise","preventionTips":["Run contract tests between client mappers and deployed proxy responses","Align proxy/client versions before rolling schema changes","Detect gateway-substituted bodies early by checking Content-Type and shape at the transport layer","Require the mandatory repo fields in any custom proxy implementation"],"tags":["proxy","contract-mismatch","parsing","client-error"],"backgroundTag":"proxy-payload-schema-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}