{"record":{"id":"769e0d01138c34c9","repo":"router-for-me/CLIProxyAPI","slug":"kimi-oauth-error-s-s","errorCode":null,"errorMessage":"kimi: OAuth error: %s - %s","messagePattern":"kimi: OAuth error: (.+?) - (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":324,"sourceCode":"\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 == \"\" {\n\t\treturn nil, fmt.Errorf(\"kimi: empty access token in response\"), false\n\t}\n\n\tvar expiresAt int64\n\tif oauthResp.ExpiresIn > 0 {\n\t\texpiresAt = time.Now().Unix() + int64(oauthResp.ExpiresIn)\n\t}\n\n\treturn &KimiTokenData{\n\t\tAccessToken:  oauthResp.AccessToken,\n\t\tRefreshToken: oauthResp.RefreshToken,\n\t\tTokenType:    oauthResp.TokenType,\n\t\tExpiresAt:    expiresAt,\n\t\tScope:        oauthResp.Scope,","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L306-L342","documentation":"The token endpoint returned an OAuth error code not in the handled set (authorization_pending, slow_down, expired_token, access_denied). Both the error code and error_description are embedded. This is the catch-all for server-side OAuth failures such as invalid_grant, invalid_client, invalid_request, or unsupported_grant_type.","triggerScenarios":"invalid_client if the hardcoded kimiClientID is invalidated by Moonshot; invalid_grant for a malformed/replayed device_code; server introduces new error codes; temporary server misconfiguration returning server_error.","commonSituations":"Client ID rotation on Kimi's side (requires updating this repo's kimiClientID constant), corrupted auths/ storage feeding a bad device code, new OAuth error variants after upstream changes.","solutions":["Read the embedded error and error_description — e.g. 'invalid_client - client not allowed' pinpoints the cause","For server_error or transient codes, retry the login after a short wait","For invalid_client, update CLIProxyAPI to the latest release (client ID fix lands upstream)","For invalid_grant, clear stale state under auths/ and start a fresh login"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func extractKimiOAuthError(err error) (code, desc string, ok bool) {\n    m := regexp.MustCompile(`kimi: OAuth error: (\\S+) - (.*)`).FindStringSubmatch(err.Error())\n    if len(m) == 3 { return m[1], m[2], true }\n    return \"\", \"\", false\n}","tryCatchPattern":"if code, desc, ok := extractKimiOAuthError(err); ok {\n    switch code {\n    case \"server_error\":\n        time.Sleep(5 * time.Second); return retry()\n    case \"invalid_client\":\n        return fmt.Errorf(\"client ID invalidated; update CLIProxyAPI\")\n    }\n}","preventionTips":["Branch on the embedded error code instead of string-matching the whole message","invalid_client means upgrade; server_error means retry","Report unhandled codes upstream"],"tags":["kimi","oauth","device-flow","oauth-error"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}