{"record":{"id":"3ff78051ed940561","repo":"can1357/oh-my-pi","slug":"invalid-copilot-billing-usage-response","errorCode":null,"errorMessage":"Invalid Copilot billing usage response","messagePattern":"Invalid Copilot billing usage response","errorType":"http","errorClass":"AIError.ProviderHttpError","httpStatus":200,"severity":"error","filePath":"packages/ai/src/usage/github-copilot.ts","lineNumber":211,"sourceCode":"\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\",\n\t\t\t},\n\t\t\tsignal,\n\t\t},\n\t);\n\n\tif (!isRecord(data)) throw new AIError.ProviderHttpError(\"Invalid Copilot billing usage response\", 200);\n\treturn data as BillingUsageResponse;\n}\n\nfunction buildLimitFromQuota(\n\tkey: string,\n\tlabel: string,\n\tquota: CopilotQuotaDetail,\n\tplan: string,\n\twindow: UsageWindow | undefined,\n\taccountId?: string,\n): UsageLimit {\n\tconst used = quota.unlimited ? undefined : Math.max(0, quota.entitlement - quota.remaining);\n\tconst limit = quota.unlimited ? undefined : quota.entitlement;\n\tconst amount = buildAmount(used, limit, \"requests\");\n\tconst notes: string[] = [];\n\tif (quota.unlimited) notes.push(\"Unlimited\");\n\tif (quota.overage_count > 0) {\n\t\tnotes.push(`Overage requests: ${quota.overage_count}`);","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/usage/github-copilot.ts#L193-L229","documentation":"Thrown by fetchBillingUsage when the GitHub Copilot billing usage endpoint returns 200 but the body is not a JSON object, so it cannot be treated as BillingUsageResponse. Like the internal-user variant, the library checks isRecord(data) and throws ProviderHttpError with status 200 to indicate a malformed payload rather than an HTTP failure.","triggerScenarios":"A successful (200) response from the billing usage endpoint whose parsed JSON is not an object — HTML from a proxy, an array, null, or a changed API response schema.","commonSituations":"Enterprise proxy or API gateway serving an interstitial page; GHE (GitHub Enterprise) base URL whose billing endpoint differs from github.com's; upstream API contract change on the billing endpoint.","solutions":["Inspect the raw 200 body to identify what the endpoint actually returned.","Confirm the base URL matches GitHub's billing API for the account type (github.com vs GHE with matching endpoint).","Bypass or correctly configure proxies that could inject HTML responses.","Upgrade the library if GitHub altered the billing usage response schema."],"exampleFix":"// before: pointing billing fetch at GHE host lacking this endpoint\nconst data = await fetchBillingUsage(ctx, \"https://ghe.corp.example/api\", ...);\n// after: use the public GitHub billing API\nconst data = await fetchBillingUsage(ctx, \"https://api.github.com\", ...);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isBillingUsage(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v) && \"quota_snapshots\" in v;\n}","tryCatchPattern":"try {\n\tconst billing = await provider.billing(params);\n} catch (err) {\n\tif (err instanceof AIError.ProviderHttpError && /Invalid Copilot billing usage response/.test(err.message)) {\n\t\t// capture raw body upstream / fall back to cached report\n\t} else throw err;\n}","preventionTips":["Use the correct billing API base for the account type (github.com vs enterprise).","Bypass HTML-injecting proxies for API hosts (allowlist api.github.com).","Watch GitHub changelogs for billing endpoint schema changes and update the library."],"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"}