Document poisoned_managed_auth field

This commit is contained in:
Eric Traut
2026-03-10 15:56:25 -06:00
committed by celia-oai
parent 2468bee659
commit 1306c64d98
2 changed files with 47 additions and 31 deletions

View File

@@ -409,6 +409,13 @@ enum EnsureConversationListenerResult {
ConnectionClosed,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RefreshTokenRequestOutcome {
NotAttemptedOrSucceeded,
FailedTransiently,
FailedPermanently,
}
pub(crate) struct CodexMessageProcessorArgs {
pub(crate) auth_manager: Arc<AuthManager>,
pub(crate) thread_manager: Arc<ThreadManager>,
@@ -1338,25 +1345,26 @@ impl CodexMessageProcessor {
}
}
async fn refresh_token_if_requested(&self, do_refresh: bool) -> bool {
async fn refresh_token_if_requested(&self, do_refresh: bool) -> RefreshTokenRequestOutcome {
if self.auth_manager.is_external_auth_active() {
return false;
return RefreshTokenRequestOutcome::NotAttemptedOrSucceeded;
}
if do_refresh && let Err(err) = self.auth_manager.refresh_token().await {
let failed_reason = err.failed_reason();
if failed_reason.is_none() {
tracing::warn!("failed to refresh token while getting account: {err}");
return RefreshTokenRequestOutcome::FailedTransiently;
}
return failed_reason.is_some();
return RefreshTokenRequestOutcome::FailedPermanently;
}
false
RefreshTokenRequestOutcome::NotAttemptedOrSucceeded
}
async fn get_auth_status(&self, request_id: ConnectionRequestId, params: GetAuthStatusParams) {
let include_token = params.include_token.unwrap_or(false);
let do_refresh = params.refresh_token.unwrap_or(false);
let refresh_failed_permanently = self.refresh_token_if_requested(do_refresh).await;
let refresh_outcome = self.refresh_token_if_requested(do_refresh).await;
// Determine whether auth is required based on the active model provider.
// If a custom provider is configured with `requires_openai_auth == false`,
@@ -1375,8 +1383,11 @@ impl CodexMessageProcessor {
Some(auth) => {
let auth_mode = auth.api_auth_mode();
let (reported_auth_method, token_opt) = if include_token
&& (refresh_failure.is_some() || refresh_failed_permanently)
{
&& (refresh_failure.is_some()
|| matches!(
refresh_outcome,
RefreshTokenRequestOutcome::FailedPermanently
)) {
(Some(auth_mode), None)
} else {
match auth.get_token() {

View File

@@ -796,11 +796,13 @@ struct CachedAuth {
auth: Option<CodexAuth>,
/// Callback used to refresh external auth by asking the parent app for new tokens.
external_refresher: Option<Arc<dyn ExternalAuthRefresher>>,
poisoned_managed_auth: Option<PoisonedManagedAuth>,
/// Permanent refresh failure cached for the current managed auth snapshot so
/// later refresh attempts for the same credentials fail fast without network.
permanent_refresh_failure: Option<PermanentRefreshFailure>,
}
#[derive(Clone, Debug)]
struct PoisonedManagedAuth {
struct PermanentRefreshFailure {
auth_dot_json: AuthDotJson,
error: RefreshTokenFailedError,
}
@@ -817,11 +819,11 @@ impl Debug for CachedAuth {
&self.external_refresher.as_ref().map(|_| "present"),
)
.field(
"poisoned_managed_auth",
"permanent_refresh_failure",
&self
.poisoned_managed_auth
.permanent_refresh_failure
.as_ref()
.map(|poisoned| poisoned.error.reason),
.map(|failure| failure.error.reason),
)
.finish()
}
@@ -1060,7 +1062,7 @@ impl AuthManager {
inner: RwLock::new(CachedAuth {
auth: managed_auth,
external_refresher: None,
poisoned_managed_auth: None,
permanent_refresh_failure: None,
}),
enable_codex_api_key_env,
auth_credentials_store_mode,
@@ -1073,7 +1075,7 @@ impl AuthManager {
let cached = CachedAuth {
auth: Some(auth),
external_refresher: None,
poisoned_managed_auth: None,
permanent_refresh_failure: None,
};
Arc::new(Self {
@@ -1090,7 +1092,7 @@ impl AuthManager {
let cached = CachedAuth {
auth: Some(auth),
external_refresher: None,
poisoned_managed_auth: None,
permanent_refresh_failure: None,
};
Arc::new(Self {
codex_home,
@@ -1108,7 +1110,7 @@ impl AuthManager {
pub fn refresh_failure(&self) -> Option<RefreshTokenFailedError> {
let auth = self.auth_cached()?;
self.refresh_failure_for_auth(&auth)
self.permanent_refresh_failure_for_auth(&auth)
}
/// Current cached auth (clone). May be `None` if not logged in or load failed.
@@ -1188,17 +1190,20 @@ impl AuthManager {
}
}
fn refresh_failure_for_auth(&self, auth: &CodexAuth) -> Option<RefreshTokenFailedError> {
fn permanent_refresh_failure_for_auth(
&self,
auth: &CodexAuth,
) -> Option<RefreshTokenFailedError> {
let auth_dot_json = auth.get_current_auth_json()?;
self.inner
.read()
.ok()
.and_then(|cached| cached.poisoned_managed_auth.clone())
.filter(|poisoned| poisoned.auth_dot_json == auth_dot_json)
.map(|poisoned| poisoned.error)
.and_then(|cached| cached.permanent_refresh_failure.clone())
.filter(|failure| failure.auth_dot_json == auth_dot_json)
.map(|failure| failure.error)
}
fn poison_managed_auth_if_unchanged(
fn record_permanent_refresh_failure_if_unchanged(
&self,
attempted_auth: &CodexAuth,
error: &RefreshTokenFailedError,
@@ -1214,7 +1219,7 @@ impl AuthManager {
.and_then(CodexAuth::get_current_auth_json)
.is_some_and(|current| current == attempted_auth_dot_json);
if current_auth_matches {
guard.poisoned_managed_auth = Some(PoisonedManagedAuth {
guard.permanent_refresh_failure = Some(PermanentRefreshFailure {
auth_dot_json: attempted_auth_dot_json,
error: error.clone(),
});
@@ -1236,18 +1241,18 @@ 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 poisoned_auth_still_matches = guard
.poisoned_managed_auth
let permanent_refresh_failure_still_matches = guard
.permanent_refresh_failure
.as_ref()
.and_then(|poisoned| {
.and_then(|failure| {
new_auth
.as_ref()
.and_then(CodexAuth::get_current_auth_json)
.map(|current| current == poisoned.auth_dot_json)
.map(|current| current == failure.auth_dot_json)
})
.unwrap_or(false);
if !poisoned_auth_still_matches {
guard.poisoned_managed_auth = None;
if !permanent_refresh_failure_still_matches {
guard.permanent_refresh_failure = None;
}
tracing::info!("Reloaded auth, changed: {changed}");
guard.auth = new_auth;
@@ -1325,7 +1330,7 @@ impl AuthManager {
pub async fn refresh_token(&self) -> Result<(), RefreshTokenError> {
let auth_before_reload = self.auth_cached();
if let Some(auth_before_reload) = auth_before_reload.as_ref()
&& let Some(error) = self.refresh_failure_for_auth(auth_before_reload)
&& let Some(error) = self.permanent_refresh_failure_for_auth(auth_before_reload)
{
return Err(RefreshTokenError::Permanent(error));
}
@@ -1359,7 +1364,7 @@ impl AuthManager {
Some(auth) => auth,
None => return Ok(()),
};
if let Some(error) = self.refresh_failure_for_auth(&auth) {
if let Some(error) = self.permanent_refresh_failure_for_auth(&auth) {
return Err(RefreshTokenError::Permanent(error));
}
@@ -1381,7 +1386,7 @@ impl AuthManager {
CodexAuth::ApiKey(_) => Ok(()),
};
if let Err(RefreshTokenError::Permanent(error)) = &result {
self.poison_managed_auth_if_unchanged(&attempted_auth, error);
self.record_permanent_refresh_failure_if_unchanged(&attempted_auth, error);
}
result
}