zed-industries/zed · warning
Sign-out occurred during token refresh
Error message
Sign-out occurred during token refresh
What it means
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.
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.
Example fix
// before
let creds = get_fresh_credentials(&state, &http_client, cx).await?;
// after
let creds = match get_fresh_credentials(&state, &http_client, cx).await {
Ok(creds) => creds,
Err(e) if e.to_string().contains("Sign-out occurred during token refresh") => {
return Err(LanguageModelCompletionError::NoApiKey { provider: PROVIDER_NAME });
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
let (auth_generation, has_creds) = state.read_with(&*cx, |s, _| (s.auth_generation, s.credentials.is_some()))?;
if !has_creds {
return Err(anyhow!("signed out; skip completion"));
} Type guard
fn is_signout_race(err: &LanguageModelCompletionError) -> bool {
matches!(err, LanguageModelCompletionError::Other(e))
&& e.to_string().contains("Sign-out occurred during token refresh")
} Try / catch
if let Err(e) = stream_open_ai_completion(request, cx).await {
if is_signout_race(&e) {
log::info!("user signed out mid-refresh; aborting stream silently");
return Ok(()); // treat as user cancellation
}
return Err(e.into());
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- {e}
- OAuth callback was cancelled
- Auth server metadata issuer mismatch: expected
- authorization server does not advertise…
- authorization server does not support S256 PKCE
AI-assisted analysis of zed-industries/zed@916fc2b8cb (2026-09-19).
Data as JSON: /api/errors/1ecb86c2c473f4d1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/x_ai_subscribed/src/x_ai_subscribed.rs:624
let previous_refresh_token = creds.refresh_token.clone();
let previous_email = creds.email.clone();
let generation = state
.read_with(&*cx, |s, _| s.auth_generation)
.map_err(LanguageModelCompletionError::Other)?;
let shared_task = cx
.spawn(async move |cx| {
let result = refresh_token(&http_client_clone, &previous_refresh_token).await;
match result {
Ok(tokens) => {
let persist_result: Result<SuperGrokCredentials, Arc<anyhow::Error>> = async {
let current_generation = state_clone
.read_with(&*cx, |s, _| s.auth_generation)
.map_err(|e| Arc::new(e))?;
if current_generation != generation {
return Err(Arc::new(anyhow!(
"Sign-out occurred during token refresh"
)));
}
let claims = tokens
.id_token
.as_deref()
.map(extract_email_claim)
.unwrap_or(None);
let refreshed = SuperGrokCredentials {
access_token: tokens.access_token,
refresh_token: tokens
.refresh_token
.unwrap_or(previous_refresh_token.clone()),
expires_at_ms: now_ms() + tokens.expires_in * 1000,
email: claims.or(tokens.email).or(previous_email.clone()),
};
View on GitHub (pinned to 916fc2b8cb)