{"record":{"id":"e33bd7d19fa97a24","repo":"router-for-me/CLIProxyAPI","slug":"kimi-refresh-token-rejected-status-d","errorCode":null,"errorMessage":"kimi: refresh token rejected (status %d)","messagePattern":"kimi: refresh token rejected \\(status (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/auth/kimi/kimi.go","lineNumber":401,"sourceCode":"\t}\n\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kimi: refresh request failed: %w\", err)\n\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"kimi refresh token: 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 refresh response: %w\", err)\n\t}\n\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","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L383-L419","documentation":"The refresh endpoint answered 401 Unauthorized or 403 Forbidden: the stored refresh token is no longer accepted by Kimi. This is the definitive 'credential dead' signal — the token was revoked, expired permanently, or the account/client was disabled. Automated refresh cannot recover; interactive re-login is required.","triggerScenarios":"User revoked the app or logged out all sessions on Kimi; refresh token past its absolute lifetime; Moonshot disabled the account; rotating refresh tokens where the old one was already consumed (singleflight normally prevents concurrent consumption, but external use of the token can).","commonSituations":"Long-running deployments whose stored auths/ credential was invalidated server-side, copying auth files between installs (second install consumes the rotation), account security resets.","solutions":["Re-run the Kimi login flow (device authorization) to mint a fresh token pair, replacing the entry in auths/","Do not share or copy the same auth file across multiple instances — rotation invalidates the old refresh token","If it recurs quickly after login, check whether another process (old instance, other machine) is using the same credential"],"exampleFix":"// before\nnewTok, err := c.deviceClient.RefreshToken(ctx, tok.RefreshToken)\nif err != nil {\n    return err // surfaces as opaque failure, requests keep failing\n}\n\n// after\nnewTok, err := c.deviceClient.RefreshToken(ctx, tok.RefreshToken)\nif err != nil {\n    if strings.Contains(err.Error(), \"refresh token rejected\") {\n        // credential is dead: mark for interactive re-login, stop retrying refresh\n        log.Errorf(\"kimi: refresh token rejected; re-login required\")\n        return errReauthRequired\n    }\n    return err\n}","handlingStrategy":"fallback","validationCode":"// Check expiry/validity heuristics before relying on the credential\nif tok.ExpiresAt > 0 && time.Until(time.Unix(tok.ExpiresAt, 0)) < 0 && strings.TrimSpace(tok.RefreshToken) == \"\" {\n    return errReauthRequired // no refresh token and already expired: go to login UI\n}","typeGuard":"func isKimiReauthRequired(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"refresh token rejected\")\n}","tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"refresh token rejected\") {\n        // terminal: disable the credential, notify user to re-login, stop retrying\n        return errReauthRequired\n    }\n    return err\n}","preventionTips":["Never copy or share auths/ credentials between instances — rotation invalidates old tokens","Mark 401/403 refreshes as re-auth-required instead of retrying","Revoke-and-relogin on account changes"],"tags":["kimi","oauth","refresh-token","auth-failure","relogin"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}