router-for-me/CLIProxyAPI · error

failed to parse refresh response: %w

Error message

failed to parse refresh response: %w

What it means

The refresh endpoint returned 200 but the body failed json.Unmarshal into the expected token struct — syntactically invalid JSON, typically rewritten or truncated by an intermediary. Note that claims parsing of the ID token (ParseJWTToken) is deliberately non-fatal (logged as warning), so this error is purely about the outer response not being parseable JSON.

Source

Thrown at internal/auth/codex/openai_auth.go:257

	body, errRead := io.ReadAll(resp.Body)
	if errRead != nil {
		return nil, fmt.Errorf("failed to read refresh response: %w", errRead)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("token refresh failed with status %d: %s", resp.StatusCode, string(body))
	}

	var tokenResp struct {
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		IDToken      string `json:"id_token"`
		TokenType    string `json:"token_type"`
		ExpiresIn    int    `json:"expires_in"`
	}

	if errUnmarshal := json.Unmarshal(body, &tokenResp); errUnmarshal != nil {
		return nil, fmt.Errorf("failed to parse refresh response: %w", errUnmarshal)
	}

	// Extract account ID from ID token
	claims, errParseJWT := ParseJWTToken(tokenResp.IDToken)
	if errParseJWT != nil {
		log.Warnf("Failed to parse refreshed ID token: %v", errParseJWT)
	}

	accountID := ""
	email := ""
	if claims != nil {
		accountID = claims.GetAccountID()
		email = claims.Email
	}

	return &CodexTokenData{
		IDToken:      tokenResp.IDToken,
		AccessToken:  tokenResp.AccessToken,

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Log the raw body at debug level to see what was actually served.
  2. Remove the intermediary for the auth domain (proxy bypass rule).
  3. Retry after confirming clean network path; persistent malformed 200s point at the middlebox, not the provider.
  4. Re-login if the stored token has meanwhile expired past recovery.
Defensive patterns

Strategy: try-catch

Try / catch

td, err := auth.RefreshTokens(ctx, rt)
if err != nil && strings.Contains(err.Error(), "failed to parse refresh response") {
    // 200 + non-JSON on refresh: suspect middleware rewriting responses
    log.Errorf("non-JSON refresh response: %v", err)
}

Prevention

When it happens

Trigger: Proxy/portal replacing the refresh response with HTML while keeping 200; truncated body from a buffering middlebox; provider incident serving a malformed payload.

Common situations: Background refresh through corporate MITM infrastructure; captive portals on servers that roamed networks; rare upstream content glitches.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/7fa8b58a46524500. Report an issue: GitHub.