{"record":{"id":"92f4a8b12e943856","repo":"router-for-me/CLIProxyAPI","slug":"xai-token-refresh-refresh-token-is-required","errorCode":null,"errorMessage":"xai token refresh: refresh token is required","messagePattern":"xai token refresh: refresh token is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/xai/xai.go","lineNumber":333,"sourceCode":"\t\t\treturn nil, fmt.Errorf(\"xai device token error: %s\", payload.Error), interval, false\n\t\t}\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"xai device token request failed with status %d: %s\", resp.StatusCode, strings.TrimSpace(string(body))), interval, false\n\t}\n\tif strings.TrimSpace(payload.AccessToken) == \"\" {\n\t\treturn nil, fmt.Errorf(\"xai device token response missing access_token\"), interval, false\n\t}\n\n\temail, subject := parseJWTIdentity(payload.IDToken)\n\treturn buildTokenData(payload.AccessToken, payload.RefreshToken, payload.IDToken, payload.TokenType, payload.ExpiresIn, email, subject), nil, interval, false\n}\n\n// RefreshTokens refreshes an xAI access token.\nfunc (a *XAIAuth) RefreshTokens(ctx context.Context, refreshToken, tokenEndpoint string) (*TokenData, error) {\n\tif strings.TrimSpace(refreshToken) == \"\" {\n\t\treturn nil, fmt.Errorf(\"xai token refresh: refresh token is required\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trefreshToken = strings.TrimSpace(refreshToken)\n\tif strings.TrimSpace(tokenEndpoint) == \"\" {\n\t\tdiscovery, errDiscover := a.Discover(ctx)\n\t\tif errDiscover != nil {\n\t\t\treturn nil, errDiscover\n\t\t}\n\t\ttokenEndpoint = discovery.TokenEndpoint\n\t}\n\ttokenEndpoint = strings.TrimSpace(tokenEndpoint)\n\n\tresult, err, _ := xaiRefreshGroup.Do(refreshToken, func() (interface{}, error) {\n\t\treturn a.refreshTokensSingleFlight(context.WithoutCancel(ctx), refreshToken, tokenEndpoint)\n\t})\n\tif err != nil {","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/xai/xai.go#L315-L351","documentation":"RefreshTokens was called with an empty or whitespace-only refresh token. The client validates inputs before any network call because a refresh without a credential is a caller-side contract violation, not a server condition.","triggerScenarios":"Calling XAIAuth.RefreshTokens(ctx, \"\", endpoint), or passing a TokenData.RefreshToken that was never populated (e.g. the device flow returned no refresh_token).","commonSituations":"Persisted auth file missing the refresh token field; token refresh job loading stale/corrupted auth state; xAI issuing access-token-only responses for some grants.","solutions":["Inspect the stored auth/credential file and confirm the refresh token field exists and is non-empty","If the device flow never returned a refresh_token, re-authenticate interactively to obtain one","Guard refresh scheduling on the presence of a refresh token before invoking RefreshTokens"],"exampleFix":"// before\nif err := svc.Refresh(ctx); err != nil { ... } // internally calls RefreshTokens with empty token\n\n// after\nif strings.TrimSpace(creds.RefreshToken) == \"\" {\n    return errors.New(\"no refresh token stored; re-authentication required\")\n}\nif err := svc.Refresh(ctx); err != nil { ... }","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(storedTokenData.RefreshToken) == \"\" {\n    return fmt.Errorf(\"no xAI refresh token stored; run interactive login again\")\n}\nnewTokens, err := auth.RefreshTokens(ctx, storedTokenData.RefreshToken, storedTokenData.TokenEndpoint)","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"refresh token is required\") {\n    // stored credentials incomplete: force re-auth instead of retrying\n    return fmt.Errorf(\"re-authentication required: %w\", err)\n}","preventionTips":["Validate stored credential files contain a refresh token before scheduling refresh","Persist TokenData from the device flow atomically so partial writes never drop the refresh token","Fall back to interactive login when the refresh token is absent rather than retrying"],"tags":["xai","auth","refresh-token","validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}