diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 563a87c3b..814fee9d8 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1354,7 +1354,7 @@ impl CodexMessageProcessor { self.config.cli_auth_credentials_store_mode, ) { Ok(()) => { - self.auth_manager.reload(); + self.auth_manager.reload().await; Ok(()) } Err(err) => Err(JSONRPCErrorError { @@ -1505,7 +1505,7 @@ impl CodexMessageProcessor { .await; if success { - auth_manager.reload(); + auth_manager.reload().await; config_manager.replace_cloud_requirements_loader( auth_manager.clone(), chatgpt_base_url, @@ -1613,7 +1613,7 @@ impl CodexMessageProcessor { .await; if success { - auth_manager.reload(); + auth_manager.reload().await; config_manager.replace_cloud_requirements_loader( auth_manager.clone(), chatgpt_base_url, @@ -1749,7 +1749,7 @@ impl CodexMessageProcessor { self.outgoing.send_error(request_id, error).await; return; } - self.auth_manager.reload(); + self.auth_manager.reload().await; self.config_manager.replace_cloud_requirements_loader( self.auth_manager.clone(), self.config.chatgpt_base_url.clone(), diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index cc4e22e92..73332394f 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -391,7 +391,8 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle { let processor_outgoing = Arc::clone(&outgoing_message_sender); let auth_manager = - AuthManager::shared_from_config(args.config.as_ref(), args.enable_codex_api_key_env); + AuthManager::shared_from_config(args.config.as_ref(), args.enable_codex_api_key_env) + .await; let config_manager = ConfigManager::new( args.config.codex_home.to_path_buf(), args.cli_overrides, diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 52b6de0e0..de8960810 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -469,7 +469,7 @@ pub async fn run_main_with_transport_options( config_manager .replace_thread_config_loader(Arc::clone(&discovered_thread_config_loader)); let auth_manager = - AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await; config_manager.replace_cloud_requirements_loader(auth_manager, config.chatgpt_base_url); } Err(err) => { @@ -631,7 +631,7 @@ pub async fn run_main_with_transport_options( } let auth_manager = - AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await; let remote_control_enabled = config.features.enabled(Feature::RemoteControl); if transport_accept_handles.is_empty() && !remote_control_enabled { @@ -712,7 +712,7 @@ pub async fn run_main_with_transport_options( let outgoing_message_sender = Arc::new(OutgoingMessageSender::new(outgoing_tx)); let outbound_control_tx = outbound_control_tx; let auth_manager = - AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await; let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs { outgoing: outgoing_message_sender, arg0_paths, diff --git a/codex-rs/app-server/src/message_processor/tracing_tests.rs b/codex-rs/app-server/src/message_processor/tracing_tests.rs index 507d7865b..850d05c04 100644 --- a/codex-rs/app-server/src/message_processor/tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor/tracing_tests.rs @@ -127,7 +127,7 @@ impl TracingHarness { let server = create_mock_responses_server_repeating_assistant("Done").await; let codex_home = TempDir::new()?; let config = Arc::new(build_test_config(codex_home.path(), &server.uri()).await?); - let (processor, outgoing_rx) = build_test_processor(config); + let (processor, outgoing_rx) = build_test_processor(config).await; let tracing = init_test_tracing(); tracing.exporter.reset(); tracing::callsite::rebuild_interest_cache(); @@ -257,7 +257,7 @@ async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result, ) -> ( Arc, @@ -266,7 +266,7 @@ fn build_test_processor( let (outgoing_tx, outgoing_rx) = mpsc::channel(16); let outgoing = Arc::new(OutgoingMessageSender::new(outgoing_tx)); let auth_manager = - AuthManager::shared_from_config(config.as_ref(), /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config.as_ref(), /*enable_codex_api_key_env*/ false).await; let config_manager = ConfigManager::new( config.codex_home.to_path_buf(), Vec::new(), diff --git a/codex-rs/app-server/src/transport/remote_control/tests.rs b/codex-rs/app-server/src/transport/remote_control/tests.rs index 6b0051f8d..82e02ae5d 100644 --- a/codex-rs/app-server/src/transport/remote_control/tests.rs +++ b/codex-rs/app-server/src/transport/remote_control/tests.rs @@ -497,7 +497,8 @@ async fn remote_control_start_allows_missing_auth_when_enabled() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; let (transport_event_tx, _transport_event_rx) = mpsc::channel::(CHANNEL_CAPACITY); let shutdown_token = CancellationToken::new(); @@ -1085,7 +1086,8 @@ async fn remote_control_waits_for_account_id_before_enrolling() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; let expected_server_name = gethostname().to_string_lossy().trim().to_string(); let expected_enrollment = RemoteControlEnrollment { account_id: "account_id".to_string(), diff --git a/codex-rs/app-server/src/transport/remote_control/websocket.rs b/codex-rs/app-server/src/transport/remote_control/websocket.rs index 464832e34..4cd078455 100644 --- a/codex-rs/app-server/src/transport/remote_control/websocket.rs +++ b/codex-rs/app-server/src/transport/remote_control/websocket.rs @@ -706,7 +706,7 @@ pub(crate) async fn load_remote_control_auth( "remote control requires ChatGPT authentication", )); } - auth_manager.reload(); + auth_manager.reload().await; reloaded = true; continue; }; @@ -714,7 +714,7 @@ pub(crate) async fn load_remote_control_auth( break auth; } if auth.get_account_id().is_none() && !reloaded { - auth_manager.reload(); + auth_manager.reload().await; reloaded = true; continue; } @@ -1090,7 +1090,8 @@ mod tests { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; let mut auth_recovery = auth_manager.unauthorized_recovery(); let mut enrollment = Some(RemoteControlEnrollment { account_id: "account_id".to_string(), @@ -1172,7 +1173,8 @@ mod tests { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; let mut auth_recovery = auth_manager.unauthorized_recovery(); let mut enrollment = None; save_auth( diff --git a/codex-rs/chatgpt/src/chatgpt_client.rs b/codex-rs/chatgpt/src/chatgpt_client.rs index 42aac4113..05d818668 100644 --- a/codex-rs/chatgpt/src/chatgpt_client.rs +++ b/codex-rs/chatgpt/src/chatgpt_client.rs @@ -21,7 +21,7 @@ pub(crate) async fn chatgpt_get_request_with_timeout( ) -> anyhow::Result { let chatgpt_base_url = &config.chatgpt_base_url; let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; let auth = auth_manager .auth() .await diff --git a/codex-rs/chatgpt/src/connectors.rs b/codex-rs/chatgpt/src/connectors.rs index 62e804094..9dba71ce3 100644 --- a/codex-rs/chatgpt/src/connectors.rs +++ b/codex-rs/chatgpt/src/connectors.rs @@ -26,7 +26,7 @@ const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60); async fn apps_enabled(config: &Config) -> bool { let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; let auth = auth_manager.auth().await; config .features @@ -35,7 +35,7 @@ async fn apps_enabled(config: &Config) -> bool { async fn connector_auth(config: &Config) -> anyhow::Result { let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; let auth = auth_manager .auth() .await diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 4fa7272ae..5c60c20cb 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -362,7 +362,9 @@ pub async fn run_login_with_device_code_fallback_to_browser( pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { let config = load_config_or_exit(cli_config_overrides).await; - match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode) { + match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode) + .await + { Ok(Some(auth)) => match auth.auth_mode() { AuthMode::ApiKey => match auth.get_token() { Ok(api_key) => { diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 9f465521d..852dff616 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1384,7 +1384,7 @@ async fn run_debug_models_command( .map_err(anyhow::Error::msg)?; let config = Config::load_with_cli_overrides(cli_overrides).await?; let auth_manager = - AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true); + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await; let models_manager = build_models_manager(&config, auth_manager, CollaborationModesConfig::default()); models_manager diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 86a12e7d1..d38fbb084 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -727,7 +727,7 @@ pub fn cloud_requirements_loader( }) } -pub fn cloud_requirements_loader_for_storage( +pub async fn cloud_requirements_loader_for_storage( codex_home: PathBuf, enable_codex_api_key_env: bool, credentials_store_mode: AuthCredentialsStoreMode, @@ -738,7 +738,8 @@ pub fn cloud_requirements_loader_for_storage( enable_codex_api_key_env, credentials_store_mode, Some(chatgpt_base_url.clone()), - ); + ) + .await; cloud_requirements_loader(auth_manager, chatgpt_base_url, codex_home) } @@ -853,7 +854,7 @@ mod tests { Ok(()) } - fn auth_manager_with_api_key() -> Arc { + async fn auth_manager_with_api_key() -> Arc { let tmp = tempdir().expect("tempdir"); let auth_json = json!({ "OPENAI_API_KEY": "sk-test-key", @@ -861,15 +862,18 @@ mod tests { "last_refresh": null, }); write_auth_json(tmp.path(), auth_json).expect("write auth"); - Arc::new(AuthManager::new( - tmp.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )) + Arc::new( + AuthManager::new( + tmp.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ) } - fn auth_manager_with_plan_and_identity( + async fn auth_manager_with_plan_and_identity( plan_type: &str, chatgpt_user_id: Option<&str>, account_id: Option<&str>, @@ -886,12 +890,15 @@ mod tests { ), ) .expect("write auth"); - Arc::new(AuthManager::new( - tmp.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )) + Arc::new( + AuthManager::new( + tmp.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ) } fn chatgpt_auth_json( @@ -975,7 +982,7 @@ mod tests { manager: Arc, } - fn managed_auth_context( + async fn managed_auth_context( plan_type: &str, chatgpt_user_id: Option<&str>, account_id: Option<&str>, @@ -995,18 +1002,22 @@ mod tests { ) .expect("write auth"); ManagedAuthContext { - manager: Arc::new(AuthManager::new( - home.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )), + manager: Arc::new( + AuthManager::new( + home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ), _home: home, } } - fn auth_manager_with_plan(plan_type: &str) -> Arc { + async fn auth_manager_with_plan(plan_type: &str) -> Arc { auth_manager_with_plan_and_identity(plan_type, Some("user-12345"), Some("account-12345")) + .await } fn parse_for_fetch(contents: Option<&str>) -> Option { @@ -1118,7 +1129,7 @@ mod tests { #[tokio::test] async fn fetch_cloud_requirements_skips_non_chatgpt_auth() { - let auth_manager = auth_manager_with_api_key(); + let auth_manager = auth_manager_with_api_key().await; let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( auth_manager, @@ -1134,7 +1145,7 @@ mod tests { async fn fetch_cloud_requirements_skips_non_business_or_enterprise_plan() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("pro"), + auth_manager_with_plan("pro").await, Arc::new(StaticFetcher { contents: None }), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1147,7 +1158,7 @@ mod tests { async fn fetch_cloud_requirements_skips_team_like_usage_based_plan() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("self_serve_business_usage_based"), + auth_manager_with_plan("self_serve_business_usage_based").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1161,7 +1172,7 @@ mod tests { async fn fetch_cloud_requirements_allows_business_plan() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1193,7 +1204,7 @@ mod tests { async fn fetch_cloud_requirements_allows_business_like_usage_based_plan() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("enterprise_cbp_usage_based"), + auth_manager_with_plan("enterprise_cbp_usage_based").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1225,7 +1236,7 @@ mod tests { async fn fetch_cloud_requirements_allows_hc_plan_as_enterprise() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("hc"), + auth_manager_with_plan("hc").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1329,7 +1340,7 @@ enabled = false #[tokio::test(start_paused = true)] async fn fetch_cloud_requirements_times_out() { - let auth_manager = auth_manager_with_plan("enterprise"); + let auth_manager = auth_manager_with_plan("enterprise").await; let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( auth_manager, @@ -1356,7 +1367,7 @@ enabled = false ])); let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1405,12 +1416,15 @@ enabled = false ), ) .expect("write initial auth"); - let auth_manager = Arc::new(AuthManager::new( - auth_home.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )); + let auth_manager = Arc::new( + AuthManager::new( + auth_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ); write_auth_json( auth_home.path(), @@ -1479,12 +1493,15 @@ enabled = false ), ) .expect("write initial auth"); - let auth_manager = Arc::new(AuthManager::new( - auth_home.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )); + let auth_manager = Arc::new( + AuthManager::new( + auth_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ); write_auth_json( auth_home.path(), @@ -1559,7 +1576,8 @@ enabled = false Some("account-12345"), "stale-access-token", "test-refresh-token", - ); + ) + .await; write_auth_json( auth._home.path(), chatgpt_auth_json( @@ -1611,12 +1629,15 @@ enabled = false ), ) .expect("write auth"); - let auth_manager = Arc::new(AuthManager::new( - auth_home.path().to_path_buf(), - /*enable_codex_api_key_env*/ false, - AuthCredentialsStoreMode::File, - /*chatgpt_base_url*/ None, - )); + let auth_manager = Arc::new( + AuthManager::new( + auth_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await, + ); let fetcher = Arc::new(UnauthorizedFetcher { message: @@ -1653,7 +1674,7 @@ enabled = false ])); let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1678,7 +1699,7 @@ enabled = false ))])); let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, fetcher, codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1700,7 +1721,7 @@ enabled = false async fn fetch_cloud_requirements_uses_cache_when_valid() { let codex_home = tempdir().expect("tempdir"); let prime_service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1711,7 +1732,7 @@ enabled = false let fetcher = Arc::new(SequenceFetcher::new(vec![Err(request_error())])); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1747,7 +1768,8 @@ enabled = false "business", /*chatgpt_user_id*/ None, Some("account-12345"), - ), + ) + .await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1790,7 +1812,7 @@ enabled = false async fn fetch_cloud_requirements_does_not_use_cache_when_auth_identity_is_incomplete() { let codex_home = tempdir().expect("tempdir"); let prime_service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1807,7 +1829,8 @@ enabled = false "business", /*chatgpt_user_id*/ None, Some("account-12345"), - ), + ) + .await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1843,7 +1866,8 @@ enabled = false "business", Some("user-12345"), Some("account-12345"), - ), + ) + .await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1860,7 +1884,8 @@ enabled = false "business", Some("user-99999"), Some("account-12345"), - ), + ) + .await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1892,7 +1917,7 @@ enabled = false async fn fetch_cloud_requirements_ignores_tampered_cache() { let codex_home = tempdir().expect("tempdir"); let prime_service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1917,7 +1942,7 @@ enabled = false "allowed_approval_policies = [\"never\"]".to_string(), ))])); let service = CloudRequirementsService::new( - auth_manager_with_plan("enterprise"), + auth_manager_with_plan("enterprise").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -1975,7 +2000,7 @@ enabled = false "allowed_approval_policies = [\"never\"]".to_string(), ))])); let service = CloudRequirementsService::new( - auth_manager_with_plan("enterprise"), + auth_manager_with_plan("enterprise").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -2007,7 +2032,7 @@ enabled = false async fn fetch_cloud_requirements_writes_signed_cache() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -2070,7 +2095,7 @@ enabled = false let fetcher = Arc::new(SequenceFetcher::new(vec![Ok(None), Err(request_error())])); let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("enterprise"), + auth_manager_with_plan("enterprise").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -2088,7 +2113,7 @@ enabled = false ])); let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan("enterprise"), + auth_manager_with_plan("enterprise").await, fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, @@ -2121,7 +2146,7 @@ enabled = false )), ])); let service = CloudRequirementsService::new( - auth_manager_with_plan("business"), + auth_manager_with_plan("business").await, fetcher, codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, diff --git a/codex-rs/cloud-tasks/src/util.rs b/codex-rs/cloud-tasks/src/util.rs index e433b892e..9a5056aa6 100644 --- a/codex-rs/cloud-tasks/src/util.rs +++ b/codex-rs/cloud-tasks/src/util.rs @@ -44,12 +44,15 @@ pub fn normalize_base_url(input: &str) -> String { pub async fn load_auth_manager(chatgpt_base_url: Option) -> Option { // TODO: pass in cli overrides once cloud tasks properly support them. let config = Config::load_with_cli_overrides(Vec::new()).await.ok()?; - Some(AuthManager::new( - config.codex_home.to_path_buf(), - /*enable_codex_api_key_env*/ false, - config.cli_auth_credentials_store_mode, - chatgpt_base_url.or(Some(config.chatgpt_base_url)), - )) + Some( + AuthManager::new( + config.codex_home.to_path_buf(), + /*enable_codex_api_key_env*/ false, + config.cli_auth_credentials_store_mode, + chatgpt_base_url.or(Some(config.chatgpt_base_url)), + ) + .await, + ) } /// Build headers for ChatGPT-backed requests: `User-Agent`, optional `Authorization`, diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index 456f3a7ea..42cd41521 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -144,7 +144,7 @@ pub async fn list_cached_accessible_connectors_from_mcp_tools( config: &Config, ) -> Option> { let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; let auth = auth_manager.auth().await; if !config .features @@ -216,7 +216,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager( environment_manager: &EnvironmentManager, ) -> anyhow::Result { let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; let auth = auth_manager.auth().await; if !config .features @@ -434,7 +434,7 @@ async fn list_directory_connectors_for_tool_suggest_with_auth( Some(auth) } else { let auth_manager = - AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await; loaded_auth = auth_manager.auth().await; loaded_auth.as_ref() }; diff --git a/codex-rs/core/src/prompt_debug.rs b/codex-rs/core/src/prompt_debug.rs index b6c66410a..f782c0d23 100644 --- a/codex-rs/core/src/prompt_debug.rs +++ b/codex-rs/core/src/prompt_debug.rs @@ -29,7 +29,7 @@ pub async fn build_prompt_input( config.ephemeral = true; let auth_manager = - AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false); + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await; let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths( config.codex_self_exe.clone(), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 888656843..7bd5cee68 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1091,7 +1091,8 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { config.model_provider = model_provider; let auth_manager = - match CodexAuth::from_auth_storage(codex_home.path(), AuthCredentialsStoreMode::File) { + match CodexAuth::from_auth_storage(codex_home.path(), AuthCredentialsStoreMode::File).await + { Ok(Some(auth)) => codex_core::test_support::auth_manager_from_auth(auth), Ok(None) => panic!("No CodexAuth found in codex_home"), Err(e) => panic!("Failed to load CodexAuth: {e}"), diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 334e4001d..9e9d9bc14 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -348,7 +348,8 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result /*enable_codex_api_key_env*/ false, config_toml.cli_auth_credentials_store.unwrap_or_default(), chatgpt_base_url, - ); + ) + .await; let run_cli_overrides = cli_kv_overrides.clone(); let run_loader_overrides = loader_overrides.clone(); let run_cloud_requirements = cloud_requirements.clone(); @@ -438,7 +439,9 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result auth_credentials_store_mode: config.cli_auth_credentials_store_mode, forced_login_method: config.forced_login_method, forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(), - }) { + }) + .await + { eprintln!("{err}"); std::process::exit(1); } diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index b38a40b6f..b6c86761e 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -116,10 +116,11 @@ fn login_with_agent_identity_rejects_invalid_jwt() { ); } -#[test] -fn missing_auth_json_returns_none() { +#[tokio::test] +async fn missing_auth_json_returns_none() { let dir = tempdir().unwrap(); let auth = CodexAuth::from_auth_storage(dir.path(), AuthCredentialsStoreMode::File) + .await .expect("call should succeed"); assert_eq!(auth, None); } @@ -143,6 +144,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .unwrap() .unwrap(); assert_eq!(None, auth.api_key()); @@ -196,6 +198,7 @@ async fn loads_api_key_from_auth_json() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .unwrap() .unwrap(); assert_eq!(auth.auth_mode(), AuthMode::ApiKey); @@ -222,15 +225,16 @@ fn logout_removes_auth_file() -> Result<(), std::io::Error> { Ok(()) } -#[test] -fn unauthorized_recovery_reports_mode_and_step_names() { +#[tokio::test] +async fn unauthorized_recovery_reports_mode_and_step_names() { let dir = tempdir().unwrap(); let manager = AuthManager::shared( dir.path().to_path_buf(), /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; let managed = UnauthorizedRecovery { manager: Arc::clone(&manager), step: UnauthorizedRecoveryStep::Reload, @@ -250,8 +254,8 @@ fn unauthorized_recovery_reports_mode_and_step_names() { assert_eq!(external.step_name(), "external_refresh"); } -#[test] -fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() { +#[tokio::test] +async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() { let codex_home = tempdir().unwrap(); write_auth_file( AuthFileParams { @@ -268,6 +272,7 @@ fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); let mut updated_auth_dot_json = auth @@ -284,6 +289,7 @@ fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() { updated_auth_dot_json, AuthCredentialsStoreMode::File, ) + .await .expect("updated auth should parse"); let manager = AuthManager::from_auth_for_testing(auth.clone()); @@ -619,9 +625,9 @@ impl Drop for EnvVarGuard { } } -#[test] +#[tokio::test] #[serial(codex_auth_env)] -fn load_auth_reads_agent_identity_from_env() { +async fn load_auth_reads_agent_identity_from_env() { let codex_home = tempdir().unwrap(); let expected_record = agent_identity_record("account-123"); let agent_identity = fake_agent_identity_jwt(&expected_record).expect("fake agent identity"); @@ -632,6 +638,7 @@ fn load_auth_reads_agent_identity_from_env() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("env auth should load") .expect("env auth should be present"); @@ -645,9 +652,9 @@ fn load_auth_reads_agent_identity_from_env() { ); } -#[test] +#[tokio::test] #[serial(codex_auth_env)] -fn load_auth_keeps_codex_api_key_env_precedence() { +async fn load_auth_keeps_codex_api_key_env_precedence() { let codex_home = tempdir().unwrap(); let record = agent_identity_record("account-123"); let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity"); @@ -659,6 +666,7 @@ fn load_auth_keeps_codex_api_key_env_precedence() { /*enable_codex_api_key_env*/ true, AuthCredentialsStoreMode::File, ) + .await .expect("env auth should load") .expect("env auth should be present"); @@ -679,8 +687,9 @@ async fn enforce_login_restrictions_logs_out_for_method_mismatch() { ) .await; - let err = - super::enforce_login_restrictions(&config).expect_err("expected method mismatch to error"); + let err = super::enforce_login_restrictions(&config) + .await + .expect_err("expected method mismatch to error"); assert!(err.to_string().contains("ChatGPT login is required")); assert!( !codex_home.path().join("auth.json").exists(), @@ -710,6 +719,7 @@ async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() { .await; let err = super::enforce_login_restrictions(&config) + .await .expect_err("expected workspace mismatch to error"); assert!(err.to_string().contains("workspace org_mine")); assert!( @@ -739,7 +749,9 @@ async fn enforce_login_restrictions_allows_matching_workspace() { ) .await; - super::enforce_login_restrictions(&config).expect("matching workspace should succeed"); + super::enforce_login_restrictions(&config) + .await + .expect("matching workspace should succeed"); assert!( codex_home.path().join("auth.json").exists(), "auth.json should remain when restrictions pass" @@ -761,7 +773,9 @@ async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_f ) .await; - super::enforce_login_restrictions(&config).expect("matching workspace should succeed"); + super::enforce_login_restrictions(&config) + .await + .expect("matching workspace should succeed"); assert!( codex_home.path().join("auth.json").exists(), "auth.json should remain when restrictions pass" @@ -782,6 +796,7 @@ async fn enforce_login_restrictions_blocks_env_api_key_when_chatgpt_required() { .await; let err = super::enforce_login_restrictions(&config) + .await .expect_err("environment API key should not satisfy forced ChatGPT login"); assert!( err.to_string() @@ -818,8 +833,8 @@ fn fake_agent_identity_jwt(record: &AgentIdentityAuthRecord) -> std::io::Result< Ok(format!("{header_b64}.{payload_b64}.{signature_b64}")) } -#[test] -fn plan_type_maps_known_plan() { +#[tokio::test] +async fn plan_type_maps_known_plan() { let codex_home = tempdir().unwrap(); let _jwt = write_auth_file( AuthFileParams { @@ -836,14 +851,15 @@ fn plan_type_maps_known_plan() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); pretty_assertions::assert_eq!(auth.account_plan_type(), Some(AccountPlanType::Pro)); } -#[test] -fn plan_type_maps_self_serve_business_usage_based_plan() { +#[tokio::test] +async fn plan_type_maps_self_serve_business_usage_based_plan() { let codex_home = tempdir().unwrap(); let _jwt = write_auth_file( AuthFileParams { @@ -860,6 +876,7 @@ fn plan_type_maps_self_serve_business_usage_based_plan() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); @@ -869,8 +886,8 @@ fn plan_type_maps_self_serve_business_usage_based_plan() { ); } -#[test] -fn plan_type_maps_enterprise_cbp_usage_based_plan() { +#[tokio::test] +async fn plan_type_maps_enterprise_cbp_usage_based_plan() { let codex_home = tempdir().unwrap(); let _jwt = write_auth_file( AuthFileParams { @@ -887,6 +904,7 @@ fn plan_type_maps_enterprise_cbp_usage_based_plan() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); @@ -896,8 +914,8 @@ fn plan_type_maps_enterprise_cbp_usage_based_plan() { ); } -#[test] -fn plan_type_maps_unknown_to_unknown() { +#[tokio::test] +async fn plan_type_maps_unknown_to_unknown() { let codex_home = tempdir().unwrap(); let _jwt = write_auth_file( AuthFileParams { @@ -914,14 +932,15 @@ fn plan_type_maps_unknown_to_unknown() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); pretty_assertions::assert_eq!(auth.account_plan_type(), Some(AccountPlanType::Unknown)); } -#[test] -fn missing_plan_type_maps_to_unknown() { +#[tokio::test] +async fn missing_plan_type_maps_to_unknown() { let codex_home = tempdir().unwrap(); let _jwt = write_auth_file( AuthFileParams { @@ -938,6 +957,7 @@ fn missing_plan_type_maps_to_unknown() { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, ) + .await .expect("load auth") .expect("auth available"); diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 85c9b6f02..ad6d64449 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -193,7 +193,7 @@ impl From for std::io::Error { } impl CodexAuth { - fn from_auth_dot_json( + async fn from_auth_dot_json( codex_home: &Path, auth_dot_json: AuthDotJson, auth_credentials_store_mode: AuthCredentialsStoreMode, @@ -234,7 +234,7 @@ impl CodexAuth { } } - pub fn from_auth_storage( + pub async fn from_auth_storage( codex_home: &Path, auth_credentials_store_mode: AuthCredentialsStoreMode, ) -> std::io::Result> { @@ -243,6 +243,7 @@ impl CodexAuth { /*enable_codex_api_key_env*/ false, auth_credentials_store_mode, ) + .await } pub fn from_agent_identity_jwt(jwt: &str) -> std::io::Result { @@ -522,6 +523,7 @@ pub async fn logout_with_revoke( auth_credentials_store_mode, /*chatgpt_base_url*/ None, ) + .await .logout_with_revoke() .await } @@ -609,12 +611,13 @@ pub struct AuthConfig { pub forced_chatgpt_workspace_id: Option, } -pub fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> { +pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> { let Some(auth) = load_auth( &config.codex_home, /*enable_codex_api_key_env*/ true, config.auth_credentials_store_mode, - )? + ) + .await? else { return Ok(()); }; @@ -714,15 +717,11 @@ fn logout_all_stores( Ok(removed_ephemeral || removed_managed) } -fn load_auth( +async fn load_auth( codex_home: &Path, enable_codex_api_key_env: bool, auth_credentials_store_mode: AuthCredentialsStoreMode, ) -> std::io::Result> { - let build_auth = |auth_dot_json: AuthDotJson, storage_mode| { - CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, storage_mode) - }; - // API key via env var takes precedence over any other auth method. if enable_codex_api_key_env && let Some(api_key) = read_codex_api_key_from_env() { return Ok(Some(CodexAuth::from_api_key(api_key.as_str()))); @@ -735,7 +734,12 @@ fn load_auth( AuthCredentialsStoreMode::Ephemeral, ); if let Some(auth_dot_json) = ephemeral_storage.load()? { - let auth = build_auth(auth_dot_json, AuthCredentialsStoreMode::Ephemeral)?; + let auth = CodexAuth::from_auth_dot_json( + codex_home, + auth_dot_json, + AuthCredentialsStoreMode::Ephemeral, + ) + .await?; return Ok(Some(auth)); } @@ -755,7 +759,9 @@ fn load_auth( None => return Ok(None), }; - let auth = build_auth(auth_dot_json, auth_credentials_store_mode)?; + let auth = + CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, auth_credentials_store_mode) + .await?; Ok(Some(auth)) } @@ -1169,6 +1175,7 @@ impl UnauthorizedRecovery { match self .manager .reload_if_account_id_matches(self.expected_account_id.as_deref()) + .await { ReloadOutcome::ReloadedChanged => { self.step = UnauthorizedRecoveryStep::RefreshToken; @@ -1279,7 +1286,7 @@ impl AuthManager { /// preferred auth method. Errors loading auth are swallowed; `auth()` will /// simply return `None` in that case so callers can treat it as an /// unauthenticated state. - pub fn new( + pub async fn new( codex_home: PathBuf, enable_codex_api_key_env: bool, auth_credentials_store_mode: AuthCredentialsStoreMode, @@ -1290,6 +1297,7 @@ impl AuthManager { enable_codex_api_key_env, auth_credentials_store_mode, ) + .await .ok() .flatten(); Self { @@ -1402,13 +1410,16 @@ impl AuthManager { /// Force a reload of the auth information from auth.json. Returns /// whether the auth value changed. - pub fn reload(&self) -> bool { + pub async fn reload(&self) -> bool { tracing::info!("Reloading auth"); - let new_auth = self.load_auth_from_storage(); + let new_auth = self.load_auth_from_storage().await; self.set_cached_auth(new_auth) } - fn reload_if_account_id_matches(&self, expected_account_id: Option<&str>) -> ReloadOutcome { + async fn reload_if_account_id_matches( + &self, + expected_account_id: Option<&str>, + ) -> ReloadOutcome { let expected_account_id = match expected_account_id { Some(account_id) => account_id, None => { @@ -1417,7 +1428,7 @@ impl AuthManager { } }; - let new_auth = self.load_auth_from_storage(); + let new_auth = self.load_auth_from_storage().await; let new_account_id = new_auth.as_ref().and_then(CodexAuth::get_account_id); if new_account_id.as_deref() != Some(expected_account_id) { @@ -1488,12 +1499,13 @@ impl AuthManager { } } - fn load_auth_from_storage(&self) -> Option { + async fn load_auth_from_storage(&self) -> Option { load_auth( &self.codex_home, self.enable_codex_api_key_env, self.auth_credentials_store_mode, ) + .await .ok() .flatten() } @@ -1557,22 +1569,25 @@ impl AuthManager { } /// Convenience constructor returning an `Arc` wrapper. - pub fn shared( + pub async fn shared( codex_home: PathBuf, enable_codex_api_key_env: bool, auth_credentials_store_mode: AuthCredentialsStoreMode, chatgpt_base_url: Option, ) -> Arc { - Arc::new(Self::new( - codex_home, - enable_codex_api_key_env, - auth_credentials_store_mode, - chatgpt_base_url, - )) + Arc::new( + Self::new( + codex_home, + enable_codex_api_key_env, + auth_credentials_store_mode, + chatgpt_base_url, + ) + .await, + ) } /// Convenience constructor returning an `Arc` wrapper from resolved config. - pub fn shared_from_config( + pub async fn shared_from_config( config: &impl AuthManagerConfig, enable_codex_api_key_env: bool, ) -> Arc { @@ -1581,7 +1596,8 @@ impl AuthManager { enable_codex_api_key_env, config.cli_auth_credentials_store_mode(), Some(config.chatgpt_base_url()), - ); + ) + .await; auth_manager.set_forced_chatgpt_workspace_id(config.forced_chatgpt_workspace_id()); auth_manager } @@ -1647,7 +1663,10 @@ impl AuthManager { .as_ref() .and_then(CodexAuth::get_account_id); - match self.reload_if_account_id_matches(expected_account_id.as_deref()) { + match self + .reload_if_account_id_matches(expected_account_id.as_deref()) + .await + { ReloadOutcome::ReloadedChanged => { tracing::info!("Skipping token refresh because auth changed after guarded reload."); Ok(()) @@ -1714,10 +1733,10 @@ impl AuthManager { /// if a file was removed, Ok(false) if no auth file existed. On success, /// reloads the in‑memory auth cache so callers immediately observe the /// unauthenticated state. - pub fn logout(&self) -> std::io::Result { + pub async fn logout(&self) -> std::io::Result { let removed = logout_all_stores(&self.codex_home, self.auth_credentials_store_mode)?; // Always reload to clear any cached auth (even if file absent). - self.reload(); + self.reload().await; Ok(removed) } @@ -1730,7 +1749,7 @@ impl AuthManager { } let result = logout_all_stores(&self.codex_home, self.auth_credentials_store_mode)?; // Always reload to clear any cached auth (even if file absent). - self.reload(); + self.reload().await; Ok(result) } @@ -1826,7 +1845,7 @@ impl AuthManager { AuthCredentialsStoreMode::Ephemeral, ) .map_err(RefreshTokenError::Transient)?; - self.reload(); + self.reload().await; Ok(()) } @@ -1846,7 +1865,7 @@ impl AuthManager { refresh_response.refresh_token, ) .map_err(RefreshTokenError::from)?; - self.reload(); + self.reload().await; Ok(()) } diff --git a/codex-rs/login/tests/suite/auth_refresh.rs b/codex-rs/login/tests/suite/auth_refresh.rs index 3ae0eb5fa..afdf466d0 100644 --- a/codex-rs/login/tests/suite/auth_refresh.rs +++ b/codex-rs/login/tests/suite/auth_refresh.rs @@ -46,7 +46,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -56,7 +56,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; ctx.auth_manager .refresh_token_from_authority() @@ -110,7 +110,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -120,7 +120,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; ctx.auth_manager .refresh_token() @@ -164,7 +164,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); @@ -175,7 +175,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); let disk_auth = AuthDotJson { @@ -230,7 +230,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -240,7 +240,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); disk_tokens.account_id = Some("other-account".to_string()); @@ -299,7 +299,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let stale_refresh = Utc::now() - Duration::days(9); let fresh_access_token = access_token_with_expiration(Utc::now() + Duration::hours(1)); let initial_tokens = build_tokens(&fresh_access_token, INITIAL_REFRESH_TOKEN); @@ -310,7 +310,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> { last_refresh: Some(stale_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let cached_auth = ctx .auth_manager @@ -347,7 +347,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let fresh_refresh = Utc::now() - Duration::days(1); let expired_access_token = access_token_with_expiration(Utc::now() - Duration::hours(1)); let initial_tokens = build_tokens(&expired_access_token, INITIAL_REFRESH_TOKEN); @@ -358,7 +358,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> { last_refresh: Some(fresh_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let cached_auth = ctx .auth_manager @@ -398,7 +398,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> { let server = MockServer::start().await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let stale_refresh = Utc::now() - Duration::days(9); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -408,7 +408,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> { last_refresh: Some(stale_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let fresh_refresh = Utc::now() - Duration::days(1); let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); @@ -461,7 +461,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let stale_refresh = Utc::now() - Duration::days(9); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -471,7 +471,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul last_refresh: Some(stale_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let fresh_refresh = Utc::now() - Duration::days(1); let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); @@ -522,7 +522,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -532,7 +532,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let err = ctx .auth_manager @@ -575,7 +575,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -585,7 +585,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let first_err = ctx .auth_manager @@ -642,7 +642,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result< .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -652,7 +652,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result< last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let first_err = ctx .auth_manager @@ -723,7 +723,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()> .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -733,7 +733,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()> last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let err = ctx .auth_manager @@ -776,7 +776,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -786,7 +786,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); let disk_auth = AuthDotJson { @@ -870,7 +870,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> { .mount(&server) .await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let initial_last_refresh = Utc::now() - Duration::days(1); let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN); let initial_auth = AuthDotJson { @@ -880,7 +880,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, }; - ctx.write_auth(&initial_auth)?; + ctx.write_auth(&initial_auth).await?; let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token"); disk_tokens.account_id = Some("other-account".to_string()); @@ -941,7 +941,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let ctx = RefreshTokenTestContext::new(&server)?; + let ctx = RefreshTokenTestContext::new(&server).await?; let auth = AuthDotJson { auth_mode: Some(AuthMode::ApiKey), openai_api_key: Some("sk-test".to_string()), @@ -949,7 +949,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> { last_refresh: None, agent_identity: None, }; - ctx.write_auth(&auth)?; + ctx.write_auth(&auth).await?; let mut recovery = ctx.auth_manager.unauthorized_recovery(); assert!(!recovery.has_next()); @@ -974,7 +974,7 @@ struct RefreshTokenTestContext { } impl RefreshTokenTestContext { - fn new(server: &MockServer) -> Result { + async fn new(server: &MockServer) -> Result { let codex_home = TempDir::new()?; let endpoint = format!("{}/oauth/token", server.uri()); @@ -985,7 +985,8 @@ impl RefreshTokenTestContext { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; Ok(Self { codex_home, @@ -1000,13 +1001,13 @@ impl RefreshTokenTestContext { .context("auth.json should exist") } - fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> { + async fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> { save_auth( self.codex_home.path(), auth_dot_json, AuthCredentialsStoreMode::File, )?; - self.auth_manager.reload(); + self.auth_manager.reload().await; Ok(()) } } diff --git a/codex-rs/login/tests/suite/logout.rs b/codex-rs/login/tests/suite/logout.rs index e703b15eb..2364ee7f5 100644 --- a/codex-rs/login/tests/suite/logout.rs +++ b/codex-rs/login/tests/suite/logout.rs @@ -143,7 +143,8 @@ async fn auth_manager_logout_with_revoke_uses_cached_auth() -> Result<()> { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, /*chatgpt_base_url*/ None, - ); + ) + .await; save_auth( codex_home.path(), &chatgpt_auth_with_refresh_token("newer-disk-refresh-token"), diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index 1d904e457..95fc4341e 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -141,7 +141,8 @@ pub async fn run_main( arg0_paths, Arc::new(config), environment_manager, - ); + ) + .await; async move { while let Some(msg) = incoming_rx.recv().await { match msg { diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index e0a1e77f6..32d05d1f9 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -49,7 +49,7 @@ pub(crate) struct MessageProcessor { impl MessageProcessor { /// Create a new `MessageProcessor`, retaining a handle to the outgoing /// `Sender` so handlers can enqueue messages to be written to stdout. - pub(crate) fn new( + pub(crate) async fn new( outgoing: OutgoingMessageSender, arg0_paths: Arg0DispatchPaths, config: Arc, @@ -59,7 +59,8 @@ impl MessageProcessor { let auth_manager = AuthManager::shared_from_config( config.as_ref(), /*enable_codex_api_key_env*/ false, - ); + ) + .await; let thread_manager = Arc::new(ThreadManager::new( config.as_ref(), auth_manager, diff --git a/codex-rs/models-manager/src/manager_tests.rs b/codex-rs/models-manager/src/manager_tests.rs index 4046b7565..57c42be01 100644 --- a/codex-rs/models-manager/src/manager_tests.rs +++ b/codex-rs/models-manager/src/manager_tests.rs @@ -206,7 +206,7 @@ fn static_manager_for_tests(model_catalog: ModelsResponse) -> StaticModelsManage ) } -fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth { +async fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth { let auth_dot_json = codex_login::AuthDotJson { auth_mode: Some(AuthMode::ChatgptAuthTokens), openai_api_key: None, @@ -232,6 +232,7 @@ c2ln", .expect("auth.json should be written"); CodexAuth::from_auth_storage(codex_home, AuthCredentialsStoreMode::File) + .await .expect("auth should load") .expect("auth should be present") } @@ -685,7 +686,7 @@ async fn refresh_available_models_fetches_with_chatgpt_auth_tokens() { "ChatGPT Auth Tokens", /*priority*/ 1, )]]); - let auth = chatgpt_auth_tokens_for_tests(codex_home.path()); + let auth = chatgpt_auth_tokens_for_tests(codex_home.path()).await; let manager = openai_manager_for_tests_with_auth( codex_home.path().to_path_buf(), endpoint.clone(), diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 46b2fd1d2..6dd527c71 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -795,7 +795,8 @@ pub async fn run_main( /*enable_codex_api_key_env*/ false, config_toml.cli_auth_credentials_store.unwrap_or_default(), chatgpt_base_url, - ); + ) + .await; let model_provider_override = if cli.oss { let resolved = resolve_oss_provider( @@ -895,7 +896,9 @@ pub async fn run_main( auth_credentials_store_mode: config.cli_auth_credentials_store_mode, forced_login_method: config.forced_login_method, forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(), - }) { + }) + .await + { eprintln!("{err}"); std::process::exit(1); } @@ -1166,7 +1169,8 @@ async fn run_ratatui_app( /*enable_codex_api_key_env*/ false, initial_config.cli_auth_credentials_store_mode, initial_config.chatgpt_base_url.clone(), - ); + ) + .await; } // If the user made an explicit trust decision, or we showed the login flow, reload config diff --git a/codex-rs/tui/src/onboarding/auth.rs b/codex-rs/tui/src/onboarding/auth.rs index 1e55b5c5d..cbe1a1fe8 100644 --- a/codex-rs/tui/src/onboarding/auth.rs +++ b/codex-rs/tui/src/onboarding/auth.rs @@ -986,7 +986,8 @@ mod tests { /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, "https://chatgpt.com/backend-api/".to_string(), - ), + ) + .await, feedback: codex_feedback::CodexFeedback::new(), log_db: None, environment_manager: Arc::new(