{"record":{"id":"4be5fbc1f3618423","repo":"can1357/oh-my-pi","slug":"invalid-copilot-usage-response","errorCode":null,"errorMessage":"Invalid Copilot usage response","messagePattern":"Invalid Copilot usage response","errorType":"http","errorClass":"AIError.ProviderHttpError","httpStatus":200,"severity":"error","filePath":"packages/ai/src/usage/github-copilot.ts","lineNumber":187,"sourceCode":"\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function fetchInternalUsage(\n\tctx: UsageFetchContext,\n\tgithubApiBaseUrl: string,\n\ttoken: string,\n\tsignal?: AbortSignal,\n): Promise<CopilotUsageResponse> {\n\tconst headers: Record<string, string> = {\n\t\t\"Content-Type\": \"application/json\",\n\t\tAccept: \"application/json\",\n\t\tAuthorization: `Bearer ${token}`,\n\t\t...OPENCODE_HEADERS,\n\t};\n\tconst data = await fetchJson(ctx, `${githubApiBaseUrl}/copilot_internal/user`, { headers, signal });\n\tif (!isRecord(data)) throw new AIError.ProviderHttpError(\"Invalid Copilot usage response\", 200);\n\treturn data as CopilotUsageResponse;\n}\n\nasync function fetchBillingUsage(\n\tctx: UsageFetchContext,\n\tbaseUrl: string,\n\tusername: string,\n\ttoken: string,\n\tsignal?: AbortSignal,\n): Promise<BillingUsageResponse> {\n\tconst data = await fetchJson(\n\t\tctx,\n\t\t`${baseUrl}/users/${encodeURIComponent(username)}/settings/billing/premium_request/usage`,\n\t\t{\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"X-GitHub-Api-Version\": \"2022-11-28\",","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/usage/github-copilot.ts#L169-L205","documentation":"Thrown when GitHub's /copilot_internal/user endpoint returns HTTP 200 but the JSON body is not a plain object, so it cannot be shaped into CopilotUsageResponse. The library validates the envelope (isRecord) before casting and refuses to continue with an unusable payload. The 200 status passed to ProviderHttpError signals the failure was payload-shaped, not transport-level.","triggerScenarios":"fetchInternalUsage receives a 200 response whose parsed JSON is null, an array, a string, or a number instead of an object — typically when an auth proxy, login page, or HTML error interstitial is served instead of the real JSON, or GitHub changes the internal endpoint's response shape.","commonSituations":"Corporate SSO proxy returning an HTML redirect/login page with status 200; captive portal Wi-Fi; hitting a mirror/base URL that is not the real GitHub API; GitHub changing the undocumented copilot_internal response contract.","solutions":["Log or capture the actual response body to see what replaced the expected JSON object.","Verify the GitHub API base URL points at api.github.com (or a transparent JSON proxy), not an HTML-serving gateway.","Re-authenticate behind SSO/proxies — complete any pending device or browser login flow.","Update the library if GitHub changed the internal endpoint shape; check for a newer version."],"exampleFix":"// before: custom proxy base URL that serves HTML on 200\nbaseUrl = \"https://gateway.corp.internal/github\";\n// after: use the real GitHub API endpoint\nbaseUrl = \"https://api.github.com\";","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n// pre-validate the same shape the library checks\nconst data: unknown = await res.json();\nif (!isRecord(data)) throw new Error(\"Copilot usage endpoint returned non-object JSON\");","tryCatchPattern":"try {\n\tconst usage = await copilot.usage(params);\n} catch (err) {\n\tif (err instanceof AIError.ProviderHttpError && err.message === \"Invalid Copilot usage response\") {\n\t\tlogger.warn(\"Copilot usage: non-JSON/proxied 200 response — check network path\");\n\t} else throw err;\n}","preventionTips":["Ensure no proxy/gateway injects HTML (login pages) into api.github.com responses.","Test from the deployment network — captive portals often return 200 HTML.","Pin/verify the library version against the current copilot_internal response shape."],"tags":["github-copilot","schema-validation","response-parsing"],"backgroundTag":"unexpected-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}