{"record":{"id":"dad3ce9b503da548","repo":"sipeed/picoclaw","slug":"parsing-usage-response-w","errorCode":null,"errorMessage":"parsing usage response: %w","messagePattern":"parsing usage response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/anthropic_usage.go","lineNumber":64,"sourceCode":"\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tif resp.StatusCode == http.StatusForbidden {\n\t\t\treturn nil, fmt.Errorf(\"insufficient scope: usage endpoint requires oauth scope\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"usage request failed (%d): %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result struct {\n\t\tFiveHour struct {\n\t\t\tUtilization float64 `json:\"utilization\"`\n\t\t} `json:\"five_hour\"`\n\t\tSevenDay struct {\n\t\t\tUtilization float64 `json:\"utilization\"`\n\t\t} `json:\"seven_day\"`\n\t}\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing usage response: %w\", err)\n\t}\n\n\treturn &AnthropicUsage{\n\t\tFiveHourUtilization: result.FiveHour.Utilization,\n\t\tSevenDayUtilization: result.SevenDay.Utilization,\n\t}, nil\n}\n","sourceCodeStart":46,"sourceCodeEnd":72,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/anthropic_usage.go#L46-L72","documentation":"json.Unmarshal(body, &result) failed at anthropic_usage.go:64 — the usage endpoint's body was not the expected JSON object with numeric five_hour/seven_day utilization fields. Since the status was 200, this means a 200 response whose body is HTML (proxy login page), truncated JSON from the read, or a schema change (e.g. utilization serialized as a string).","triggerScenarios":"Transparent proxy returning an HTML block page with 200; body read partially after a mid-stream stall so JSON is cut mid-token; API returning utilization as \"0.42\" (string) instead of 0.42 (float) — Unmarshal then fails with a type-error pointing at the field.","commonSituations":"Captive portals / Zscaler-style interceptors; flaky mobile links truncating bodies; Anthropic API schema evolution not yet reflected in this client version.","solutions":["Log the raw body (it is in your hands at the call site via the error's context or by reproducing with curl) to see whether it is HTML, truncated, or a schema change","Reproduce with curl -H \"Authorization: Bearer $TOKEN\" against the usage endpoint to inspect the true body","Bypass or configure the intercepting proxy for the Anthropic domain","If the JSON schema genuinely changed, update/pin the client version that matches the API"],"exampleFix":"// before: assume body is always the expected shape\njson.Unmarshal(body, &result)\n\n// after: sniff non-JSON bodies before decoding\nif len(body) == 0 || (body[0] != '{' && body[0] != '[') {\n    return nil, fmt.Errorf(\"usage endpoint returned non-JSON body: %.120s\", body)\n}","handlingStrategy":"validation","validationCode":"// sniff body shape before decoding\nfunc looksLikeJSON(b []byte) bool {\n    t := strings.TrimSpace(string(b))\n    return strings.HasPrefix(t, \"{\") || strings.HasPrefix(t, \"[\")\n}\n\nif !looksLikeJSON(body) {\n    return nil, fmt.Errorf(\"usage endpoint returned non-JSON (%.120s); proxy interference?\", body)\n}","typeGuard":null,"tryCatchPattern":"if err := json.Unmarshal(body, &result); err != nil {\n    if typeErr, ok := err.(*json.UnmarshalTypeError); ok {\n        // 200 body but wrong types (e.g. utilization as string): schema drift\n        log.Printf(\"usage schema changed at field %s: %v\", typeErr.Field, typeErr)\n    }\n    return nil, fmt.Errorf(\"parsing usage response: %w\", err)\n}","preventionTips":["Reject non-JSON 200 bodies early instead of feeding them to Unmarshal","Bypass intercepting proxies for the API domain","Pin the client version to the API version you validated against"],"tags":["json","parsing","http","proxy","anthropic"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}