{"record":{"id":"4de0e03161d91fca","repo":"chenhg5/cc-connect","slug":"decode-usage-response-w","errorCode":null,"errorMessage":"decode usage response: %w","messagePattern":"decode usage response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/codex/usage.go","lineNumber":115,"sourceCode":"\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+tokens.AccessToken)\n\treq.Header.Set(\"ChatGPT-Account-Id\", tokens.AccountID)\n\treq.Header.Set(\"User-Agent\", \"codex-cli\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"request usage endpoint: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn nil, fmt.Errorf(\"usage endpoint returned status %d: %s\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\n\tvar payload codexUsageResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode usage response: %w\", err)\n\t}\n\n\treturn mapCodexUsage(payload), nil\n}\n\nfunc mapCodexUsage(payload codexUsageResponse) *core.UsageReport {\n\treport := &core.UsageReport{\n\t\tProvider:  \"codex\",\n\t\tAccountID: payload.AccountID,\n\t\tUserID:    payload.UserID,\n\t\tEmail:     payload.Email,\n\t\tPlan:      payload.PlanType,\n\t}\n\n\tif payload.RateLimit != nil {\n\t\treport.Buckets = append(report.Buckets, core.UsageBucket{\n\t\t\tName:         \"Rate limit\",\n\t\t\tAllowed:      payload.RateLimit.Allowed,","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/codex/usage.go#L97-L133","documentation":"After a 200 response, fetchUsage decodes the JSON body into codexUsageResponse. This error wraps the json.Decoder failure when the body is not the expected usage JSON — an HTML error page, an empty body, or a schema change in the backend response. It indicates the server answered 200 but with unusable content.","triggerScenarios":"json.NewDecoder(resp.Body).Decode(&payload) fails in fetchUsage after a 200 response: proxy captive portal returns HTML with 200, CDN/auth layer returns empty body, or the usage endpoint schema changed. Callers: GetUsage.","commonSituations":"Corporate proxies/captive portals returning 200 HTML login pages; auth edge cases where a 200 carries an error object instead of usage data; OpenAI changing the usage endpoint response shape after an API update.","solutions":["Log/capture the raw response body to see what the 200 actually contained","Check whether an auth/proxy layer is intercepting the request (captive portal, SSO redirect)","Pin/upgrade cc-connect to a version matching the current Codex usage endpoint schema","Retry later if it's a transient backend anomaly; verify with a manual curl of the endpoint"],"exampleFix":"// before\nusage, err := agent.GetUsage(ctx)\n// after: detect a non-JSON 200 body early\nif resp, _ := http.Get(usageURL); resp != nil {\n    var probe any\n    if err := json.NewDecoder(resp.Body).Decode(&probe); err != nil {\n        log.Fatalf(\"usage endpoint returned non-JSON 200 body — check proxy/captive portal\")\n    }\n}\nusage, err := agent.GetUsage(ctx)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"_, err := agent.GetUsage(ctx)\nif err != nil && strings.Contains(err.Error(), \"decode usage response\") {\n    // 200 with non-JSON body: capture raw body and surface it\n    log.Printf(\"unexpected usage payload; check proxy/captive portal; raw: %s\", lastRawBody)\n}","preventionTips":["Bypass or correctly authenticate corporate proxies","Log raw response bodies on decode failure for diagnosis","Pin cc-connect/Codex versions known to match the endpoint schema","Add a schema smoke-test after upgrading either component"],"tags":["go","json","http","codex"],"backgroundTag":"json-decode-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}