Propagate resolved proxy policy through auth routing (#34649)

## Why

Auth routing represented the default proxy policy as an absent configuration,
leaving consumers to reconstruct the fallback HTTP client independently. Carry
the application's resolved policy explicitly so auth and related requests use
the same client configuration.

## What changed

- Build `AuthRouteConfig` from the resolved `HttpClientFactory` for both
  `ReqwestDefault` and `RespectSystemProxy` policies.
- Pass that route configuration through login, auth, cloud task, and cloud
  configuration flows, and reuse its factory when loading cloud configuration.
- Log cloud-task configuration load failures before falling back to the
  transport-default proxy policy.

## Testing

Extended configuration tests to verify that effective and bootstrap auth routes
select the expected proxy policy when system-proxy support is enabled or
disabled by feature requirements.

GitOrigin-RevId: d03802e2660f5cf6d940e8e718ec5c82ede7eab1
This commit is contained in:
Michael Bolin
2026-07-22 01:30:44 +00:00
committed by copyberry
parent 4a443994bd
commit f899a79c03
13 changed files with 65 additions and 38 deletions
@@ -464,7 +464,7 @@ impl AccountRequestProcessor {
config.forced_chatgpt_workspace_id.clone(),
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
config.auth_route_config(),
Some(config.auth_route_config()),
)
};
#[cfg(debug_assertions)]
+8 -8
View File
@@ -180,7 +180,7 @@ pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) ->
forced_chatgpt_workspace_id,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
config.auth_route_config(),
Some(config.auth_route_config()),
)
.await
{
@@ -246,7 +246,7 @@ pub async fn run_login_with_access_token(
config.forced_chatgpt_workspace_id.as_deref(),
Some(&config.chatgpt_base_url),
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await
{
@@ -320,7 +320,7 @@ pub async fn run_login_with_device_code(
&config.codex_home,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await;
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
@@ -330,7 +330,7 @@ pub async fn run_login_with_device_code(
forced_chatgpt_workspace_id,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_route_config,
Some(auth_route_config),
);
if let Some(iss) = issuer_base_url {
opts.issuer = iss;
@@ -368,7 +368,7 @@ pub async fn run_login_with_device_code_fallback_to_browser(
&config.codex_home,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await;
@@ -379,7 +379,7 @@ pub async fn run_login_with_device_code_fallback_to_browser(
forced_chatgpt_workspace_id,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_route_config,
Some(auth_route_config),
);
if let Some(iss) = issuer_base_url {
opts.issuer = iss;
@@ -430,7 +430,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
config.cli_auth_credentials_store_mode,
Some(&config.chatgpt_base_url),
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await
{
@@ -484,7 +484,7 @@ pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
&config.codex_home,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await
{
+1 -1
View File
@@ -1756,7 +1756,7 @@ async fn load_exec_server_remote_auth_provider(
let auth = CodexAuth::from_agent_identity_jwt(
&agent_identity_jwt,
Some(&config.chatgpt_base_url),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await?;
return Ok(codex_model_provider::auth_provider_from_auth(&auth));
+1 -1
View File
@@ -610,7 +610,7 @@ pub(crate) async fn load_cli_auth_mode(config: &Config) -> Option<AuthMode> {
config.cli_auth_credentials_store_mode,
Some(&config.chatgpt_base_url),
config.auth_keyring_backend_kind(),
auth_route_config.as_ref(),
Some(&auth_route_config),
)
.await
.ok()
+3 -7
View File
@@ -6,7 +6,6 @@ use codex_config::CloudConfigBundleLoadErrorCode;
use codex_config::CloudConfigBundleLoader;
use codex_config::types::AuthCredentialsStoreMode;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_login::AuthKeyringBackendKind;
use codex_login::AuthManager;
use codex_login::AuthRouteConfig;
@@ -65,12 +64,9 @@ pub async fn cloud_config_bundle_loader_for_storage(
credentials_store_mode: AuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
chatgpt_base_url: String,
auth_route_config: Option<AuthRouteConfig>,
auth_route_config: AuthRouteConfig,
) -> CloudConfigBundleLoader {
let http_client_factory = match auth_route_config.as_ref() {
Some(config) => config.http_client_factory().clone(),
None => HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
};
let http_client_factory = auth_route_config.http_client_factory().clone();
let auth_manager = AuthManager::shared(
codex_home.clone(),
enable_codex_api_key_env,
@@ -78,7 +74,7 @@ pub async fn cloud_config_bundle_loader_for_storage(
/*forced_chatgpt_workspace_id*/ None,
Some(chatgpt_base_url.clone()),
keyring_backend_kind,
auth_route_config,
Some(auth_route_config),
)
.await;
cloud_config_bundle_loader(
+2 -1
View File
@@ -58,11 +58,12 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
#[cfg(debug_assertions)]
if use_mock {
let http_client_factory = HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault);
return Ok(BackendContext {
backend: Arc::new(codex_cloud_tasks_mock_client::MockClient),
base_url,
environment_http: RouteAwareClientPool::new_without_request_logging(
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
http_client_factory,
ClientRouteClass::Api,
),
});
+10 -6
View File
@@ -47,11 +47,15 @@ pub async fn load_auth_manager(
chatgpt_base_url: Option<String>,
) -> (Option<AuthManager>, HttpClientFactory) {
// TODO: pass in cli overrides once cloud tasks properly support them.
let Some(config) = Config::load_with_cli_overrides(Vec::new()).await.ok() else {
return (
None,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
);
let config = match Config::load_with_cli_overrides(Vec::new()).await {
Ok(config) => config,
Err(error) => {
append_error_log(format!(
"failed to load auth config; using transport-default proxy handling: {error}"
));
let http_client_factory = HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault);
return (None, http_client_factory);
}
};
let http_client_factory = config.http_client_factory();
let auth_manager = AuthManager::new(
@@ -61,7 +65,7 @@ pub async fn load_auth_manager(
config.forced_chatgpt_workspace_id.clone(),
chatgpt_base_url.or(Some(config.chatgpt_base_url.clone())),
config.auth_keyring_backend_kind(),
config.auth_route_config(),
Some(config.auth_route_config()),
)
.await;
(Some(auth_manager), http_client_factory)
+19
View File
@@ -1712,6 +1712,13 @@ respect_system_proxy = true
config.http_client_factory().outbound_proxy_policy(),
codex_http_client::OutboundProxyPolicy::RespectSystemProxy
);
assert_eq!(
config
.auth_route_config()
.http_client_factory()
.outbound_proxy_policy(),
codex_http_client::OutboundProxyPolicy::RespectSystemProxy
);
assert_eq!(
config.plugins_config_input().remote_plugin_service_config(),
codex_core_plugins::remote::RemotePluginServiceConfig::new(
@@ -1747,6 +1754,12 @@ respect_system_proxy = true
&configured,
Some(&disabled)
)?);
assert_eq!(
resolve_bootstrap_auth_route_config(&configured, Some(&disabled))?
.http_client_factory()
.outbound_proxy_policy(),
codex_http_client::OutboundProxyPolicy::ReqwestDefault
);
let configured = ConfigToml::default();
let enabled = Sourced::new(
@@ -1759,6 +1772,12 @@ respect_system_proxy = true
&configured,
Some(&enabled)
)?);
assert_eq!(
resolve_bootstrap_auth_route_config(&configured, Some(&enabled))?
.http_client_factory()
.outbound_proxy_policy(),
codex_http_client::OutboundProxyPolicy::RespectSystemProxy
);
Ok(())
}
+13 -7
View File
@@ -1251,7 +1251,7 @@ impl AuthManagerConfig for Config {
}
fn auth_route_config(&self) -> Option<AuthRouteConfig> {
Config::auth_route_config(self)
Some(Config::auth_route_config(self))
}
}
@@ -1514,9 +1514,9 @@ impl Config {
}
}
pub fn auth_route_config(&self) -> Option<AuthRouteConfig> {
self.respect_system_proxy
.then(AuthRouteConfig::respect_system_proxy)
/// Returns auth routing resolved from the effective feature configuration.
pub fn auth_route_config(&self) -> AuthRouteConfig {
AuthRouteConfig::from_http_client_factory(self.http_client_factory())
}
/// Creates the HTTP client factory resolved from the effective feature configuration.
@@ -2899,9 +2899,15 @@ pub fn resolve_bootstrap_respect_system_proxy(
pub fn resolve_bootstrap_auth_route_config(
cfg: &ConfigToml,
feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,
) -> std::io::Result<Option<AuthRouteConfig>> {
resolve_bootstrap_respect_system_proxy(cfg, feature_requirements)
.map(|enabled| enabled.then(AuthRouteConfig::respect_system_proxy))
) -> std::io::Result<AuthRouteConfig> {
resolve_bootstrap_respect_system_proxy(cfg, feature_requirements).map(|respect_system_proxy| {
let outbound_proxy_policy = if respect_system_proxy {
OutboundProxyPolicy::RespectSystemProxy
} else {
OutboundProxyPolicy::ReqwestDefault
};
AuthRouteConfig::from_http_client_factory(HttpClientFactory::new(outbound_proxy_policy))
})
}
pub(crate) fn resolve_web_search_mode_for_turn(
+1 -1
View File
@@ -491,7 +491,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
forced_login_method: config.forced_login_method,
forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),
chatgpt_base_url: Some(config.chatgpt_base_url.clone()),
auth_route_config,
auth_route_config: Some(auth_route_config),
})
.await
{
+3 -3
View File
@@ -1,5 +1,4 @@
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
/// Auth-layer adapter around client-owned proxy policy.
///
@@ -11,9 +10,10 @@ pub struct AuthRouteConfig {
}
impl AuthRouteConfig {
pub fn respect_system_proxy() -> Self {
/// Adapts an application-resolved HTTP client factory for auth requests.
pub fn from_http_client_factory(http_client_factory: HttpClientFactory) -> Self {
Self {
http_client_factory: HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy),
http_client_factory,
}
}
+1 -1
View File
@@ -1197,7 +1197,7 @@ pub async fn run_main(
forced_login_method: config.forced_login_method,
forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),
chatgpt_base_url: Some(config.chatgpt_base_url.clone()),
auth_route_config,
auth_route_config: Some(auth_route_config),
})
.await
{
+2 -1
View File
@@ -1040,6 +1040,7 @@ mod tests {
.build()
.await
.unwrap();
let auth_route_config = config.auth_route_config();
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -1052,7 +1053,7 @@ mod tests {
AuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
"https://chatgpt.com/backend-api/".to_string(),
/*auth_route_config*/ None,
auth_route_config,
)
.await,
feedback: codex_feedback::CodexFeedback::new(),