{"record":{"id":"2210461e9909ce8e","repo":"router-for-me/CLIProxyAPI","slug":"kimi-failed-to-parse-refresh-response-w","errorCode":null,"errorMessage":"kimi: failed to parse refresh response: %w","messagePattern":"kimi: failed to parse refresh response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":417,"sourceCode":"\n\tif resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {\n\t\treturn nil, fmt.Errorf(\"kimi: refresh token rejected (status %d)\", resp.StatusCode)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"kimi: refresh failed with status %d: %s\", resp.StatusCode, string(bodyBytes))\n\t}\n\n\tvar tokenResp struct {\n\t\tAccessToken  string  `json:\"access_token\"`\n\t\tRefreshToken string  `json:\"refresh_token\"`\n\t\tTokenType    string  `json:\"token_type\"`\n\t\tExpiresIn    float64 `json:\"expires_in\"`\n\t\tScope        string  `json:\"scope\"`\n\t}\n\n\tif err = json.Unmarshal(bodyBytes, &tokenResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: failed to parse refresh response: %w\", err)\n\t}\n\n\tif tokenResp.AccessToken == \"\" {\n\t\treturn nil, fmt.Errorf(\"kimi: empty access token in refresh response\")\n\t}\n\n\tvar expiresAt int64\n\tif tokenResp.ExpiresIn > 0 {\n\t\texpiresAt = time.Now().Unix() + int64(tokenResp.ExpiresIn)\n\t}\n\n\treturn &KimiTokenData{\n\t\tAccessToken:  tokenResp.AccessToken,\n\t\tRefreshToken: tokenResp.RefreshToken,\n\t\tTokenType:    tokenResp.TokenType,\n\t\tExpiresAt:    expiresAt,\n\t\tScope:        tokenResp.Scope,\n\t}, nil","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L399-L435","documentation":"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.","triggerScenarios":"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.","commonSituations":"Intercepting proxies on the auth host in long-running deployments, CDN challenge pages hitting background refresh, upstream schema drift after Kimi updates.","solutions":["Log bodyBytes before unmarshal to identify HTML vs truncated vs reshaped JSON","Retry once — challenge pages are often transient","If the schema changed, update the anonymous struct at kimi.go:407 or upgrade CLIProxyAPI"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// In wrappers: cheap pre-check that the 200 body is JSON before parsing\nif !bytes.HasPrefix(bytes.TrimSpace(bodyBytes), []byte(\"{\")) {\n    return nil, fmt.Errorf(\"kimi: non-JSON refresh response: %.120s\", bodyBytes)\n}","typeGuard":null,"tryCatchPattern":"var synErr *json.SyntaxError\nif errors.As(err, &synErr) {\n    // log body; if HTML -> proxy/WAF; if truncated -> retry; if reshaped -> schema drift\n}","preventionTips":["Log raw refresh bodies on parse failure","Keep auth.kimi.com out of intercepting proxies","Upgrade CLIProxyAPI when token endpoint schemas change"],"tags":["kimi","oauth","refresh-token","json","parsing"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}