From 445c687cdffa3176e78b216b90e7660dc82b5967 Mon Sep 17 00:00:00 2001 From: Cooper Gamble Date: Wed, 20 May 2026 23:08:05 +0000 Subject: [PATCH] [codex-cli] harden startup ChatGPT refresh [ci changed_files] --- codex-rs/cloud-requirements/src/lib.rs | 28 +++++++++++++++----------- codex-rs/exec/src/lib.rs | 12 +++++++++-- codex-rs/login/src/auth/manager.rs | 12 ++++++++++- codex-rs/tui/src/lib.rs | 11 ++++++++-- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 8fcb1e25e4..6846723036 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -760,12 +760,12 @@ pub async fn cloud_requirements_loader_for_storage( cloud_requirements_loader(auth_manager, chatgpt_base_url, codex_home) } -pub async fn cloud_requirements_loader_for_storage_with_startup_refresh( +pub async fn refresh_managed_chatgpt_token_for_storage_if_near_expiry( codex_home: PathBuf, enable_codex_api_key_env: bool, credentials_store_mode: AuthCredentialsStoreMode, chatgpt_base_url: String, -) -> CloudRequirementsLoader { +) { let auth_manager = cloud_requirements_auth_manager_for_storage( &codex_home, enable_codex_api_key_env, @@ -773,25 +773,29 @@ pub async fn cloud_requirements_loader_for_storage_with_startup_refresh( &chatgpt_base_url, ) .await; - match timeout( - CHATGPT_ACCESS_TOKEN_STARTUP_REFRESH_TIMEOUT, - auth_manager.refresh_managed_chatgpt_token_if_near_expiry(), - ) - .await - { - Ok(Ok(())) => {} - Ok(Err(err)) => { + let refresh_task = tokio::spawn(async move { + auth_manager + .refresh_managed_chatgpt_token_if_near_expiry() + .await + }); + match timeout(CHATGPT_ACCESS_TOKEN_STARTUP_REFRESH_TIMEOUT, refresh_task).await { + Ok(Ok(Ok(()))) => {} + Ok(Ok(Err(err))) => { tracing::warn!( "failed to proactively refresh ChatGPT access token during CLI startup: {err}" ); } + Ok(Err(err)) => { + tracing::warn!( + "startup ChatGPT access token refresh task failed before completion: {err}" + ); + } Err(_) => { tracing::warn!( - "timed out proactively refreshing ChatGPT access token during CLI startup" + "timed out waiting for proactive ChatGPT access token refresh during CLI startup; refresh will continue in the background" ); } } - cloud_requirements_loader(auth_manager, chatgpt_base_url, codex_home) } fn parse_cloud_requirements( diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 2aa197500e..e025be4fd8 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -51,7 +51,8 @@ use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStartedNotification; use codex_arg0::Arg0DispatchPaths; -use codex_cloud_requirements::cloud_requirements_loader_for_storage_with_startup_refresh; +use codex_cloud_requirements::cloud_requirements_loader_for_storage; +use codex_cloud_requirements::refresh_managed_chatgpt_token_for_storage_if_near_expiry; use codex_config::ConfigLoadError; use codex_config::ConfigLoadOptions; use codex_config::LoaderOverrides; @@ -362,7 +363,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result .clone() .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); // TODO(gt): Make cloud requirements failures blocking once we can fail-closed. - let cloud_requirements = cloud_requirements_loader_for_storage_with_startup_refresh( + let cloud_requirements = cloud_requirements_loader_for_storage( codex_home.to_path_buf(), /*enable_codex_api_key_env*/ false, config_toml.cli_auth_credentials_store.unwrap_or_default(), @@ -455,6 +456,13 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result } set_default_client_residency_requirement(config.enforce_residency.value()); + refresh_managed_chatgpt_token_for_storage_if_near_expiry( + config.codex_home.to_path_buf(), + /*enable_codex_api_key_env*/ false, + config.cli_auth_credentials_store_mode, + config.chatgpt_base_url.clone(), + ) + .await; if let Err(err) = enforce_login_restrictions(&AuthConfig { codex_home: config.codex_home.to_path_buf(), diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 0c8c3df2fb..7e0dac9762 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -1694,6 +1694,10 @@ impl AuthManager { REFRESH_TOKEN_UNKNOWN_MESSAGE.to_string(), )) })?; + self.refresh_token_with_refresh_lock_held().await + } + + async fn refresh_token_with_refresh_lock_held(&self) -> Result<(), RefreshTokenError> { let auth_before_reload = self.auth_cached(); if auth_before_reload .as_ref() @@ -1747,9 +1751,15 @@ impl AuthManager { return Ok(()); } + let _refresh_guard = self.refresh_lock.acquire().await.map_err(|_| { + RefreshTokenError::Permanent(RefreshTokenFailedError::new( + RefreshTokenFailedReason::Other, + REFRESH_TOKEN_UNKNOWN_MESSAGE.to_string(), + )) + })?; let _refresh_lock = self.acquire_chatgpt_startup_refresh_lock().await?; - self.refresh_token().await + self.refresh_token_with_refresh_lock_held().await } async fn acquire_chatgpt_startup_refresh_lock(&self) -> Result { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 22eb9b48ea..b25b887447 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -38,7 +38,7 @@ use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadSortKey as AppServerThreadSortKey; use codex_app_server_protocol::ThreadSourceKind; use codex_cloud_requirements::cloud_requirements_loader_for_storage; -use codex_cloud_requirements::cloud_requirements_loader_for_storage_with_startup_refresh; +use codex_cloud_requirements::refresh_managed_chatgpt_token_for_storage_if_near_expiry; use codex_config::CloudRequirementsLoader; use codex_config::ConfigLoadError; use codex_config::LoaderOverrides; @@ -985,7 +985,7 @@ pub async fn run_main( .chatgpt_base_url .clone() .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); - let cloud_requirements = cloud_requirements_loader_for_storage_with_startup_refresh( + let cloud_requirements = cloud_requirements_loader_for_storage( codex_home.to_path_buf(), /*enable_codex_api_key_env*/ false, config_toml.cli_auth_credentials_store.unwrap_or_default(), @@ -1141,6 +1141,13 @@ pub async fn run_main( } set_default_client_residency_requirement(config.enforce_residency.value()); + refresh_managed_chatgpt_token_for_storage_if_near_expiry( + config.codex_home.to_path_buf(), + /*enable_codex_api_key_env*/ false, + config.cli_auth_credentials_store_mode, + config.chatgpt_base_url.clone(), + ) + .await; if let Some(warning) = add_dir_warning_message( &cli.add_dir,