{"record":{"id":"899e666f2c417b84","repo":"router-for-me/CLIProxyAPI","slug":"kimi-failed-to-parse-token-response-w","errorCode":null,"errorMessage":"kimi: failed to parse token response: %w","messagePattern":"kimi: failed to parse token response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":310,"sourceCode":"\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: failed to read token response: %w\", err), false\n\t}\n\n\t// Parse response - Kimi returns 200 for both success and pending states\n\tvar oauthResp struct {\n\t\tError            string  `json:\"error\"`\n\t\tErrorDescription string  `json:\"error_description\"`\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, &oauthResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: failed to parse token response: %w\", err), false\n\t}\n\n\tif oauthResp.Error != \"\" {\n\t\tswitch oauthResp.Error {\n\t\tcase \"authorization_pending\":\n\t\t\treturn nil, nil, true // Continue polling\n\t\tcase \"slow_down\":\n\t\t\treturn nil, nil, true // Continue polling (with increased interval handled by caller)\n\t\tcase \"expired_token\":\n\t\t\treturn nil, fmt.Errorf(\"kimi: device code expired\"), false\n\t\tcase \"access_denied\":\n\t\t\treturn nil, fmt.Errorf(\"kimi: access denied by user\"), false\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"kimi: OAuth error: %s - %s\", oauthResp.Error, oauthResp.ErrorDescription), false\n\t\t}\n\t}\n\n\tif oauthResp.AccessToken == \"\" {","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L292-L328","documentation":"The token endpoint returned a 200 response whose body is not valid JSON. Kimi returns 200 for both success and pending states, so any parse failure means the body is not the expected OAuth JSON — e.g. an HTML page or a truncated payload. The wrapped json error identifies the syntax problem.","triggerScenarios":"WAF/Cloudflare HTML page served with 200 on /api/oauth/token, response truncated by proxy so braces are unbalanced, Moonshot schema change adding unexpected content-type or BOM-prefixed JSON.","commonSituations":"Intercepting proxies on the token host, regional CDN nodes serving challenge pages, upstream API changes after Kimi updates (fix upstream in this repo).","solutions":["Log bodyBytes before unmarshal to identify what came back (HTML challenge vs truncated JSON)","Bypass or correctly configure proxies for auth.kimi.com and retry login","Update CLIProxyAPI — if the response contract changed, the parser is fixed in newer releases"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// In wrappers/forks: sanity-check body looks like OAuth JSON before unmarshal\nif !bytes.HasPrefix(bytes.TrimSpace(bodyBytes), []byte(\"{\")) {\n    return nil, nil, fmt.Errorf(\"kimi: non-JSON token response: %.120s\", bodyBytes)\n}","typeGuard":null,"tryCatchPattern":"var synErr *json.SyntaxError\nif errors.As(err, &synErr) {\n    // log body, check for WAF/proxy HTML, retry once\n}","preventionTips":["Log raw token responses on parse failure","Keep proxies off the auth host","Update CLIProxyAPI when Kimi changes response schemas"],"tags":["kimi","oauth","device-flow","json","parsing"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}