plandex-ai/plandex · error
refresh failed - decode: %w
Error message
refresh failed - decode: %w
What it means
refreshCreds wraps a json.Decoder failure while parsing the token endpoint's 200 response into types.OauthResponse. A 200 was received, but the body is not the expected JSON shape — the response was truncated, is HTML from an intercepting proxy, or the API contract changed.
Source
Thrown at app/cli/lib/claude_max.go:323
req.Header.Set("anthropic-beta", shared.AnthropicClaudeMaxBetaHeader)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, 0, fmt.Errorf("refresh failed - http: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, fmt.Errorf("refresh failed - read body: %w", err)
}
return nil, resp.StatusCode, fmt.Errorf("refresh failed - status %d: %s", resp.StatusCode, b)
}
var r types.OauthResponse
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
return nil, 0, fmt.Errorf("refresh failed - decode: %w", err)
}
newCreds := &types.OauthCreds{
OauthResponse: r,
ExpiresAt: time.Now().Add(time.Duration(r.ExpiresIn) * time.Second),
}
// persist updated creds
accountCreds.ClaudeMax = newCreds
if err := SetAccountCredentials(accountCreds); err != nil {
return nil, 0, fmt.Errorf("refresh failed - save: %w", err)
}
return newCreds, resp.StatusCode, nil
}
View on GitHub (pinned to e2d772072e)
Solutions
- Capture and log the raw body (read it fully first, then json.Unmarshal) so you can see what was actually returned.
- Check for a captive portal or proxy injecting HTML; bypass the proxy for the token host.
- Verify the client/API version pairing — update types.OauthResponse if the token endpoint schema changed.
- Retry on transient truncation; add Content-Length validation before decoding.
Example fix
// before
var r types.OauthResponse
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
return nil, 0, fmt.Errorf("refresh failed - decode: %w", err)
}
// after
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
var r types.OauthResponse
if err := json.Unmarshal(b, &r); err != nil {
return nil, 0, fmt.Errorf("refresh failed - decode: %w; body=%q", err, string(b))
}
if r.RefreshToken == "" {
return nil, 0, fmt.Errorf("refresh failed - missing refresh_token in response")
} Defensive patterns
Strategy: type-guard
Validate before calling
b, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return fmt.Errorf("cannot read 200 response body: %w", err)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return fmt.Errorf("unexpected content type %q from token endpoint", ct)
} Type guard
func validOauthResponse(r *types.OauthResponse) bool {
return r != nil && r.AccessToken != "" && r.RefreshToken != "" && r.ExpiresIn > 0
} Try / catch
var r types.OauthResponse
if err := json.Unmarshal(b, &r); err != nil {
log.Printf("token endpoint returned non-JSON 200 body: %q", string(b))
return fmt.Errorf("refresh failed - decode: %w", err)
}
if !validOauthResponse(&r) {
return fmt.Errorf("refresh failed - incomplete oauth response")
} Prevention
- Log raw response bodies on decode failure for diagnosis.
- Validate required fields (access_token, refresh_token, expires_in) after decoding.
- Check Content-Type before decoding JSON.
- Keep types.OauthResponse in sync with the API schema on version upgrades.
When it happens
Trigger: json.NewDecoder(resp.Body).Decode(&r) errors on a 200 response: empty body, HTML/text error page disguised as 200, truncated JSON, or new/renamed fields breaking types.OauthResponse unmarshaling.
Common situations: Corporate proxies or captive portals returning HTML with status 200, server bug returning an empty 200, API version change altering the OauthResponse schema, or gzip/encoding mishandling.
Related errors
- refresh failed - marshal: %w
- error unmarshalling JSON file: %v
- error unmarshalling JSON file: %v
- error parsing embedded schema %s: %v
- error parsing schema JSON: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/252f4b2ffcf8c10f.
Report an issue: GitHub.