{"record":{"id":"1ecb86c2c473f4d1","repo":"zed-industries/zed","slug":"sign-out-occurred-during-token-refresh-x-ai-subscribed","errorCode":null,"errorMessage":"Sign-out occurred during token refresh","messagePattern":"Sign-out occurred during token refresh","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/x_ai_subscribed/src/x_ai_subscribed.rs","lineNumber":624,"sourceCode":"    let previous_refresh_token = creds.refresh_token.clone();\n    let previous_email = creds.email.clone();\n\n    let generation = state\n        .read_with(&*cx, |s, _| s.auth_generation)\n        .map_err(LanguageModelCompletionError::Other)?;\n\n    let shared_task = cx\n        .spawn(async move |cx| {\n            let result = refresh_token(&http_client_clone, &previous_refresh_token).await;\n\n            match result {\n                Ok(tokens) => {\n                    let persist_result: Result<SuperGrokCredentials, Arc<anyhow::Error>> = async {\n                        let current_generation = state_clone\n                            .read_with(&*cx, |s, _| s.auth_generation)\n                            .map_err(|e| Arc::new(e))?;\n                        if current_generation != generation {\n                            return Err(Arc::new(anyhow!(\n                                \"Sign-out occurred during token refresh\"\n                            )));\n                        }\n\n                        let claims = tokens\n                            .id_token\n                            .as_deref()\n                            .map(extract_email_claim)\n                            .unwrap_or(None);\n                        let refreshed = SuperGrokCredentials {\n                            access_token: tokens.access_token,\n                            refresh_token: tokens\n                                .refresh_token\n                                .unwrap_or(previous_refresh_token.clone()),\n                            expires_at_ms: now_ms() + tokens.expires_in * 1000,\n                            email: claims.or(tokens.email).or(previous_email.clone()),\n                        };\n","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/zed-industries/zed/blob/916fc2b8cb3a815cbef4a3b40e13081be72036b6/crates/x_ai_subscribed/src/x_ai_subscribed.rs#L606-L642","documentation":"This error is raised inside the spawned token-refresh task when the credentials were refreshed successfully but the auth_generation counter on the State entity changed between refresh start and persist time. auth_generation is bumped on sign-out, so this indicates the user signed out while the refresh was in flight; persisting the refreshed credentials would resurrect a session the user explicitly terminated, so the refresh result is discarded with this error.","triggerScenarios":"A SuperGrok token refresh completes, but between capture of `generation` (state.auth_generation at refresh start) and the post-refresh check, something (sign_in/sign_out, fatal refresh clearing auth state) changed s.auth_generation. Concretely: user signs out while stream_open_ai_completion's background refresh is still awaiting the token endpoint.","commonSituations":"User clicks Sign out (or signs into a different account) while completion requests are in flight and a token refresh is running in the background; stale completion request tries to persist credentials for a signed-out session.","solutions":["Treat as benign: retry the operation; a new get_fresh_credentials call will observe no credentials and surface NoApiKey, prompting normal re-authentication.","Verify the sign-out path bumps auth_generation so this race guard fires correctly.","Catch this error in completion streaming and stop the stream instead of showing a scary error to the user.","Ensure UI sign-out cancels or detaches in-flight completion tasks to reduce occurrence."],"exampleFix":"// before\nlet creds = get_fresh_credentials(&state, &http_client, cx).await?;\n// after\nlet creds = match get_fresh_credentials(&state, &http_client, cx).await {\n    Ok(creds) => creds,\n    Err(e) if e.to_string().contains(\"Sign-out occurred during token refresh\") => {\n        return Err(LanguageModelCompletionError::NoApiKey { provider: PROVIDER_NAME });\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"let (auth_generation, has_creds) = state.read_with(&*cx, |s, _| (s.auth_generation, s.credentials.is_some()))?;\nif !has_creds {\n    return Err(anyhow!(\"signed out; skip completion\"));\n}","typeGuard":"fn is_signout_race(err: &LanguageModelCompletionError) -> bool {\n    matches!(err, LanguageModelCompletionError::Other(e))\n        && e.to_string().contains(\"Sign-out occurred during token refresh\")\n}","tryCatchPattern":"if let Err(e) = stream_open_ai_completion(request, cx).await {\n    if is_signout_race(&e) {\n        log::info!(\"user signed out mid-refresh; aborting stream silently\");\n        return Ok(()); // treat as user cancellation\n    }\n    return Err(e.into());\n}","preventionTips":["Bump auth_generation on every sign-in/sign-out so the guard is reliable","Cancel or detach in-flight completion tasks when the user signs out","Surface this as a benign cancellation in the UI, not an error dialog","Add an integration test covering sign-out during background refresh"],"tags":["oauth","race-condition","sign-out","async"],"backgroundTag":"invalid-state-transition","analyzedSha":"916fc2b8cb3a815cbef4a3b40e13081be72036b6","analyzedAt":"2026-09-19T19:09:50.599Z","contentChangedAt":"2026-09-19T19:09:50.599Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}