{"record":{"id":"2159aefb6753274c","repo":"router-for-me/CLIProxyAPI","slug":"kimi-refresh-token-is-required","errorCode":null,"errorMessage":"kimi: refresh token is required","messagePattern":"kimi: refresh token is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":349,"sourceCode":"\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,\n\t}, nil, false\n}\n\n// RefreshToken exchanges a refresh token for a new access token.\nfunc (c *DeviceFlowClient) RefreshToken(ctx context.Context, refreshToken string) (*KimiTokenData, error) {\n\tif strings.TrimSpace(refreshToken) == \"\" {\n\t\treturn nil, fmt.Errorf(\"kimi: refresh token is required\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trefreshToken = strings.TrimSpace(refreshToken)\n\n\tresult, err, _ := kimiRefreshGroup.Do(refreshToken, func() (interface{}, error) {\n\t\treturn c.refreshTokenSingleFlight(context.WithoutCancel(ctx), refreshToken)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttokenData, ok := result.(*KimiTokenData)\n\tif !ok || tokenData == nil {\n\t\treturn nil, fmt.Errorf(\"kimi: refresh token failed: invalid single-flight result\")\n\t}\n\treturn tokenData, nil\n}","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L331-L367","documentation":"RefreshToken was called with an empty or whitespace-only refresh token string. This is pure input validation before any network call — the stored credential is missing/blank, indicating corrupted or incomplete auth state rather than a server rejection.","triggerScenarios":"Token storage under auths/ was hand-edited or partially written so refresh_token is empty; code path loads a KimiTokenStorage JSON that predates refresh tokens; caller passes the wrong variable.","commonSituations":"Truncated JSON in the auth file after a crash during save, migration from an older storage format, manual tampering with auths/*.json.","solutions":["Inspect the Kimi entry under auths/ — the refresh token field is empty; re-login to regenerate the full credential","Delete the broken auth file and run the Kimi login flow again to write a fresh one","If it recurs, check for crashes or concurrent writes corrupting auth storage"],"exampleFix":"// before\nrefreshToken := strings.TrimSpace(tokenStorage.RefreshToken)\nnewTok, err := c.deviceClient.RefreshToken(ctx, refreshToken) // crashes into validation error when empty\n\n// after\nrefreshToken := strings.TrimSpace(tokenStorage.RefreshToken)\nif refreshToken == \"\" {\n    return fmt.Errorf(\"kimi: no refresh token stored; re-login required\")\n}\nnewTok, err := c.deviceClient.RefreshToken(ctx, refreshToken)","handlingStrategy":"validation","validationCode":"refreshToken := strings.TrimSpace(tok.RefreshToken)\nif refreshToken == \"\" {\n    return fmt.Errorf(\"kimi: stored refresh token missing; re-login required\")\n}","typeGuard":"func hasKimiRefreshToken(t *KimiTokenStorage) bool {\n    return t != nil && strings.TrimSpace(t.RefreshToken) != \"\"\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"refresh token is required\") {\n    // trigger interactive re-login; do not retry with the same blank value\n}","preventionTips":["Validate stored credentials at load time","Write auth files atomically to avoid truncated JSON","Never hand-edit auths/*.json"],"tags":["kimi","oauth","refresh-token","validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}