chenhg5/cc-connect · error

decode usage response: %w

Error message

decode usage response: %w

What it means

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.

Source

Thrown at agent/codex/usage.go:115

	}
	req.Header.Set("Authorization", "Bearer "+tokens.AccessToken)
	req.Header.Set("ChatGPT-Account-Id", tokens.AccountID)
	req.Header.Set("User-Agent", "codex-cli")

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request usage endpoint: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
		return nil, fmt.Errorf("usage endpoint returned status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}

	var payload codexUsageResponse
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return nil, fmt.Errorf("decode usage response: %w", err)
	}

	return mapCodexUsage(payload), nil
}

func mapCodexUsage(payload codexUsageResponse) *core.UsageReport {
	report := &core.UsageReport{
		Provider:  "codex",
		AccountID: payload.AccountID,
		UserID:    payload.UserID,
		Email:     payload.Email,
		Plan:      payload.PlanType,
	}

	if payload.RateLimit != nil {
		report.Buckets = append(report.Buckets, core.UsageBucket{
			Name:         "Rate limit",
			Allowed:      payload.RateLimit.Allowed,

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log/capture the raw response body to see what the 200 actually contained
  2. Check whether an auth/proxy layer is intercepting the request (captive portal, SSO redirect)
  3. Pin/upgrade cc-connect to a version matching the current Codex usage endpoint schema
  4. Retry later if it's a transient backend anomaly; verify with a manual curl of the endpoint

Example fix

// before
usage, err := agent.GetUsage(ctx)
// after: detect a non-JSON 200 body early
if resp, _ := http.Get(usageURL); resp != nil {
    var probe any
    if err := json.NewDecoder(resp.Body).Decode(&probe); err != nil {
        log.Fatalf("usage endpoint returned non-JSON 200 body — check proxy/captive portal")
    }
}
usage, err := agent.GetUsage(ctx)
Defensive patterns

Strategy: try-catch

Try / catch

_, err := agent.GetUsage(ctx)
if err != nil && strings.Contains(err.Error(), "decode usage response") {
    // 200 with non-JSON body: capture raw body and surface it
    log.Printf("unexpected usage payload; check proxy/captive portal; raw: %s", lastRawBody)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/4de0e03161d91fca. Report an issue: GitHub.