router-for-me/CLIProxyAPI · error

kimi: failed to parse refresh response: %w

Error message

kimi: failed to parse refresh response: %w

What it means

The refresh endpoint returned 200 but the body failed to parse as the expected token JSON. Same failure class as error 228, on the refresh path: an HTML challenge page, truncated body, or changed response schema will trip json.Unmarshal here.

Source

Thrown at internal/auth/kimi/kimi.go:417

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return nil, fmt.Errorf("kimi: refresh token rejected (status %d)", resp.StatusCode)
	}

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

	var tokenResp struct {
		AccessToken  string  `json:"access_token"`
		RefreshToken string  `json:"refresh_token"`
		TokenType    string  `json:"token_type"`
		ExpiresIn    float64 `json:"expires_in"`
		Scope        string  `json:"scope"`
	}

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

	if tokenResp.AccessToken == "" {
		return nil, fmt.Errorf("kimi: empty access token in refresh response")
	}

	var expiresAt int64
	if tokenResp.ExpiresIn > 0 {
		expiresAt = time.Now().Unix() + int64(tokenResp.ExpiresIn)
	}

	return &KimiTokenData{
		AccessToken:  tokenResp.AccessToken,
		RefreshToken: tokenResp.RefreshToken,
		TokenType:    tokenResp.TokenType,
		ExpiresAt:    expiresAt,
		Scope:        tokenResp.Scope,
	}, nil

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Log bodyBytes before unmarshal to identify HTML vs truncated vs reshaped JSON
  2. Retry once — challenge pages are often transient
  3. If the schema changed, update the anonymous struct at kimi.go:407 or upgrade CLIProxyAPI
Defensive patterns

Strategy: validation

Validate before calling

// In wrappers: cheap pre-check that the 200 body is JSON before parsing
if !bytes.HasPrefix(bytes.TrimSpace(bodyBytes), []byte("{")) {
    return nil, fmt.Errorf("kimi: non-JSON refresh response: %.120s", bodyBytes)
}

Try / catch

var synErr *json.SyntaxError
if errors.As(err, &synErr) {
    // log body; if HTML -> proxy/WAF; if truncated -> retry; if reshaped -> schema drift
}

Prevention

When it happens

Trigger: WAF/interstitial HTML served with 200 on /api/oauth/token during refresh, truncated response from proxies, Moonshot changes refresh response field names so AccessToken/RefreshToken fields no longer unmarshal as expected.

Common situations: Intercepting proxies on the auth host in long-running deployments, CDN challenge pages hitting background refresh, upstream schema drift after Kimi updates.

Understand the failure class

Related errors


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