sipeed/picoclaw · error
parsing usage response: %w
Error message
parsing usage response: %w
What it means
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).
Source
Thrown at pkg/auth/anthropic_usage.go:64
}
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusForbidden {
return nil, fmt.Errorf("insufficient scope: usage endpoint requires oauth scope")
}
return nil, fmt.Errorf("usage request failed (%d): %s", resp.StatusCode, string(body))
}
var result struct {
FiveHour struct {
Utilization float64 `json:"utilization"`
} `json:"five_hour"`
SevenDay struct {
Utilization float64 `json:"utilization"`
} `json:"seven_day"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("parsing usage response: %w", err)
}
return &AnthropicUsage{
FiveHourUtilization: result.FiveHour.Utilization,
SevenDayUtilization: result.SevenDay.Utilization,
}, nil
}
View on GitHub (pinned to 49183d7e8d)
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
Example fix
// before: assume body is always the expected shape
json.Unmarshal(body, &result)
// after: sniff non-JSON bodies before decoding
if len(body) == 0 || (body[0] != '{' && body[0] != '[') {
return nil, fmt.Errorf("usage endpoint returned non-JSON body: %.120s", body)
} Defensive patterns
Strategy: validation
Validate before calling
// sniff body shape before decoding
func looksLikeJSON(b []byte) bool {
t := strings.TrimSpace(string(b))
return strings.HasPrefix(t, "{") || strings.HasPrefix(t, "[")
}
if !looksLikeJSON(body) {
return nil, fmt.Errorf("usage endpoint returned non-JSON (%.120s); proxy interference?", body)
} Try / catch
if err := json.Unmarshal(body, &result); err != nil {
if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
// 200 body but wrong types (e.g. utilization as string): schema drift
log.Printf("usage schema changed at field %s: %v", typeErr.Field, typeErr)
}
return nil, fmt.Errorf("parsing usage response: %w", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Captive portals / Zscaler-style interceptors; flaky mobile links truncating bodies; Anthropic API schema evolution not yet reflected in this client version.
Related errors
- failed to unmarshal response: %w
- Failed to save config
- parse response: %w
- decode JSON response: %w
- insufficient scope: usage endpoint requires oauth scope
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/dad3ce9b503da548.
Report an issue: GitHub.