[codex-cli] harden startup ChatGPT refresh [ci changed_files]

This commit is contained in:
Cooper Gamble
2026-05-20 23:08:05 +00:00
parent 4e0397eea7
commit 445c687cdf
4 changed files with 46 additions and 17 deletions

View File

@@ -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(

View File

@@ -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(),

View File

@@ -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<File, RefreshTokenError> {

View File

@@ -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,