mirror of
https://github.com/openai/codex.git
synced 2026-05-22 03:54:18 +00:00
[codex-cli] eagerly refresh ChatGPT access token on startup [ci changed_files]
This commit is contained in:
@@ -51,7 +51,7 @@ 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;
|
||||
use codex_cloud_requirements::cloud_requirements_loader;
|
||||
use codex_config::ConfigLoadError;
|
||||
use codex_config::ConfigLoadOptions;
|
||||
use codex_config::LoaderOverrides;
|
||||
@@ -71,6 +71,7 @@ use codex_core::path_utils;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_login::AuthConfig;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::default_client::set_default_client_residency_requirement;
|
||||
use codex_login::default_client::set_default_originator;
|
||||
use codex_login::enforce_login_restrictions;
|
||||
@@ -363,13 +364,21 @@ 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(
|
||||
let cloud_auth_manager = AuthManager::shared(
|
||||
codex_home.to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
config_toml.cli_auth_credentials_store.unwrap_or_default(),
|
||||
chatgpt_base_url,
|
||||
Some(chatgpt_base_url.clone()),
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = cloud_auth_manager.refresh_managed_chatgpt_token().await {
|
||||
warn!("failed to proactively refresh ChatGPT access token during CLI startup: {err}");
|
||||
}
|
||||
let cloud_requirements = cloud_requirements_loader(
|
||||
cloud_auth_manager,
|
||||
chatgpt_base_url,
|
||||
codex_home.to_path_buf(),
|
||||
);
|
||||
let run_cli_overrides = cli_kv_overrides.clone();
|
||||
let run_loader_overrides = loader_overrides.clone();
|
||||
let run_cloud_requirements = cloud_requirements.clone();
|
||||
|
||||
@@ -1715,6 +1715,19 @@ impl AuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh managed ChatGPT auth even when its access token has not expired yet.
|
||||
///
|
||||
/// CLI startup uses this to begin sessions with a freshly issued access token.
|
||||
/// Other auth modes either cannot be refreshed locally or have separate refresh
|
||||
/// ownership, so they intentionally no-op here.
|
||||
pub async fn refresh_managed_chatgpt_token(&self) -> Result<(), RefreshTokenError> {
|
||||
if !matches!(self.auth_cached(), Some(CodexAuth::Chatgpt(_))) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.refresh_token().await
|
||||
}
|
||||
|
||||
/// Attempt to refresh the current auth token from the authority that issued
|
||||
/// the token. On success, reloads the auth state from disk so other components
|
||||
/// observe refreshed token. If the token refresh fails, returns the error to
|
||||
|
||||
@@ -158,6 +158,60 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[serial_test::serial(auth_refresh)]
|
||||
#[tokio::test]
|
||||
async fn refresh_managed_chatgpt_token_refreshes_even_when_auth_is_fresh() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/oauth/token"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"access_token": "new-access-token",
|
||||
"refresh_token": "new-refresh-token"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let ctx = RefreshTokenTestContext::new(&server).await?;
|
||||
let initial_last_refresh = Utc::now();
|
||||
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
|
||||
let initial_auth = AuthDotJson {
|
||||
auth_mode: Some(AuthMode::Chatgpt),
|
||||
openai_api_key: None,
|
||||
tokens: Some(initial_tokens.clone()),
|
||||
last_refresh: Some(initial_last_refresh),
|
||||
agent_identity: None,
|
||||
};
|
||||
ctx.write_auth(&initial_auth).await?;
|
||||
|
||||
ctx.auth_manager
|
||||
.refresh_managed_chatgpt_token()
|
||||
.await
|
||||
.context("managed ChatGPT refresh should succeed")?;
|
||||
|
||||
let refreshed_tokens = TokenData {
|
||||
access_token: "new-access-token".to_string(),
|
||||
refresh_token: "new-refresh-token".to_string(),
|
||||
..initial_tokens.clone()
|
||||
};
|
||||
let stored = ctx.load_auth()?;
|
||||
let tokens = stored.tokens.as_ref().context("tokens should exist")?;
|
||||
assert_eq!(tokens, &refreshed_tokens);
|
||||
let refreshed_at = stored
|
||||
.last_refresh
|
||||
.as_ref()
|
||||
.context("last_refresh should be recorded")?;
|
||||
assert!(
|
||||
*refreshed_at >= initial_last_refresh,
|
||||
"last_refresh should advance"
|
||||
);
|
||||
|
||||
server.verify().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[serial_test::serial(auth_refresh)]
|
||||
#[tokio::test]
|
||||
async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_app_server_protocol::ThreadListCwdFilter;
|
||||
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;
|
||||
use codex_cloud_requirements::cloud_requirements_loader_for_storage;
|
||||
use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::ConfigLoadError;
|
||||
@@ -45,6 +46,7 @@ use codex_config::format_config_error_with_source;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_login::AuthConfig;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_login::default_client::set_default_client_residency_requirement;
|
||||
use codex_login::enforce_login_restrictions;
|
||||
@@ -985,13 +987,21 @@ 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(
|
||||
let cloud_auth_manager = AuthManager::shared(
|
||||
codex_home.to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
config_toml.cli_auth_credentials_store.unwrap_or_default(),
|
||||
chatgpt_base_url,
|
||||
Some(chatgpt_base_url.clone()),
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = cloud_auth_manager.refresh_managed_chatgpt_token().await {
|
||||
warn!("failed to proactively refresh ChatGPT access token during CLI startup: {err}");
|
||||
}
|
||||
let cloud_requirements = cloud_requirements_loader(
|
||||
cloud_auth_manager,
|
||||
chatgpt_base_url,
|
||||
codex_home.to_path_buf(),
|
||||
);
|
||||
|
||||
let model_provider_override = if cli.oss {
|
||||
let resolved = resolve_oss_provider(
|
||||
|
||||
Reference in New Issue
Block a user