diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 8c5e2080e6..a3c74af818 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1366,29 +1366,30 @@ impl CodexMessageProcessor { } } else { let refresh_failure = self.auth_manager.refresh_failure(); + let permanent_refresh_failure = refresh_failure.is_some() + || matches!( + refresh_outcome, + RefreshTokenRequestOutcome::FailedPermanently + ); match self.auth_manager.auth().await { Some(auth) => { let auth_mode = auth.api_auth_mode(); - let (reported_auth_method, token_opt) = if include_token - && (refresh_failure.is_some() - || matches!( - refresh_outcome, - RefreshTokenRequestOutcome::FailedPermanently - )) { - (Some(auth_mode), None) - } else { - match auth.get_token() { - Ok(token) if !token.is_empty() => { - let tok = if include_token { Some(token) } else { None }; - (Some(auth_mode), tok) + let (reported_auth_method, token_opt) = + if include_token && permanent_refresh_failure { + (Some(auth_mode), None) + } else { + match auth.get_token() { + Ok(token) if !token.is_empty() => { + let tok = if include_token { Some(token) } else { None }; + (Some(auth_mode), tok) + } + Ok(_) => (None, None), + Err(err) => { + tracing::warn!("failed to get token for auth status: {err}"); + (None, None) + } } - Ok(_) => (None, None), - Err(err) => { - tracing::warn!("failed to get token for auth status: {err}"); - (None, None) - } - } - }; + }; GetAuthStatusResponse { auth_method: reported_auth_method, auth_token: token_opt, diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index 3ac1fc01b0..2bf86472e4 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -815,15 +815,9 @@ struct CachedAuth { auth: Option, /// Callback used to refresh external auth by asking the parent app for new tokens. external_refresher: Option>, - /// Permanent refresh failure cached for the current managed auth snapshot so + /// Permanent refresh failure cached for the current auth snapshot so /// later refresh attempts for the same credentials fail fast without network. - permanent_refresh_failure: Option, -} - -#[derive(Clone, Debug)] -struct PermanentRefreshFailure { - auth_dot_json: AuthDotJson, - error: RefreshTokenFailedError, + permanent_refresh_failure: Option, } impl Debug for CachedAuth { @@ -842,7 +836,7 @@ impl Debug for CachedAuth { &self .permanent_refresh_failure .as_ref() - .map(|failure| failure.error.reason), + .map(|failure| failure.reason), ) .finish() } @@ -1142,39 +1136,34 @@ impl AuthManager { } } + /// Returns the cached permanent refresh failure only when `auth` still + /// matches the current cached auth snapshot. fn permanent_refresh_failure_for_auth( &self, auth: &CodexAuth, ) -> Option { - let auth_dot_json = auth.get_current_auth_json()?; self.inner .read() .ok() .and_then(|cached| cached.permanent_refresh_failure.clone()) - .filter(|failure| failure.auth_dot_json == auth_dot_json) - .map(|failure| failure.error) + .filter(|_| { + let current_auth = self.auth_cached(); + Self::auths_equal_for_refresh(Some(auth), current_auth.as_ref()) + }) } + /// Records a permanent refresh failure only if the failed refresh was + /// attempted against the auth snapshot that is still cached. fn record_permanent_refresh_failure_if_unchanged( &self, attempted_auth: &CodexAuth, error: &RefreshTokenFailedError, ) { - let Some(attempted_auth_dot_json) = attempted_auth.get_current_auth_json() else { - return; - }; - if let Ok(mut guard) = self.inner.write() { - let current_auth_matches = guard - .auth - .as_ref() - .and_then(CodexAuth::get_current_auth_json) - .is_some_and(|current| current == attempted_auth_dot_json); + let current_auth_matches = + Self::auths_equal_for_refresh(Some(attempted_auth), guard.auth.as_ref()); if current_auth_matches { - guard.permanent_refresh_failure = Some(PermanentRefreshFailure { - auth_dot_json: attempted_auth_dot_json, - error: error.clone(), - }); + guard.permanent_refresh_failure = Some(error.clone()); } } } @@ -1193,17 +1182,9 @@ impl AuthManager { if let Ok(mut guard) = self.inner.write() { let previous = guard.auth.as_ref(); let changed = !AuthManager::auths_equal(previous, new_auth.as_ref()); - let permanent_refresh_failure_still_matches = guard - .permanent_refresh_failure - .as_ref() - .and_then(|failure| { - new_auth - .as_ref() - .and_then(CodexAuth::get_current_auth_json) - .map(|current| current == failure.auth_dot_json) - }) - .unwrap_or(false); - if !permanent_refresh_failure_still_matches { + let auth_changed_for_refresh = + !Self::auths_equal_for_refresh(previous, new_auth.as_ref()); + if auth_changed_for_refresh { guard.permanent_refresh_failure = None; } tracing::info!("Reloaded auth, changed: {changed}");