{"record":{"id":"d758ba87936e97d7","repo":"router-for-me/CLIProxyAPI","slug":"kimi-failed-to-parse-device-code-response-w","errorCode":null,"errorMessage":"kimi: failed to parse device code response: %w","messagePattern":"kimi: failed to parse device code response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":216,"sourceCode":"\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"kimi device code: close body error: %v\", errClose)\n\t\t}\n\t}()\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: failed to read device code response: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"kimi: device code request failed with status %d: %s\", resp.StatusCode, string(bodyBytes))\n\t}\n\n\tvar deviceCode DeviceCodeResponse\n\tif err = json.Unmarshal(bodyBytes, &deviceCode); err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: failed to parse device code response: %w\", err)\n\t}\n\n\treturn &deviceCode, nil\n}\n\n// PollForToken polls the token endpoint until the user authorizes or the device code expires.\nfunc (c *DeviceFlowClient) PollForToken(ctx context.Context, deviceCode *DeviceCodeResponse) (*KimiTokenData, error) {\n\tif deviceCode == nil {\n\t\treturn nil, fmt.Errorf(\"kimi: device code is nil\")\n\t}\n\n\tinterval := time.Duration(deviceCode.Interval) * time.Second\n\tif interval < defaultPollInterval {\n\t\tinterval = defaultPollInterval\n\t}\n\n\tdeadline := time.Now().Add(maxPollDuration)\n\tif deviceCode.ExpiresIn > 0 {","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L198-L234","documentation":"A 200 response from the device authorization endpoint contained a body that is not valid JSON matching DeviceCodeResponse. The client expects fields like device_code, user_code, verification_uri, interval, expires_in. An HTML error page, empty body, or changed schema will fail json.Unmarshal.","triggerScenarios":"auth.kimi.com returns 200 with an HTML interstitial (Cloudflare challenge, consent page), a WAF-modified response, a body truncated by the proxy so JSON is incomplete, or Moonshot renames response fields so the struct no longer matches.","commonSituations":"Cloudflare/WAF injection on the auth host, transparent proxies rewriting responses, API schema changes after a Kimi update (fix upstream), locale-dependent error pages returned with status 200.","solutions":["Log or print the raw body bytes before unmarshal to see what the server actually returned (HTML vs JSON vs empty)","Retry once — some WAF challenges are one-time; a fresh request may get the real JSON","Disable interfering proxies for auth.kimi.com or add it to a proxy bypass list","If the JSON shape genuinely changed, update DeviceCodeResponse in internal/auth/kimi/kimi.go or pull the latest CLIProxyAPI release"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-check the raw body before unmarshal in forks/tests\nif len(bodyBytes) == 0 || bodyBytes[0] != '{' {\n    return nil, fmt.Errorf(\"kimi: device code response is not JSON: %.120s\", bodyBytes)\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    var syntaxErr *json.SyntaxError\n    if errors.As(err, &syntaxErr) {\n        // body was not JSON: check proxies / WAF, retry once\n    }\n    return err\n}","preventionTips":["Check Content-Type of responses in debug logs before assuming API JSON","Bypass proxies for auth.kimi.com","Log raw bodies on parse failures to diagnose HTML challenges fast"],"tags":["kimi","oauth","device-flow","json","parsing"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}