mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
Skip background workspace routing reads without ChatGPT auth (#52635)
## Why Background workspace routing reads reload configuration even when signed out or using an API key, although routing requires ChatGPT authentication. ## What changed Check cached authentication before reading accounts for workspace routing at app-server startup and connection initialization. Skip these background reads without ChatGPT authentication, while preserving configuration reloads for explicit `account/read` requests. ## Testing Add regression tests covering signed-out and API-key startup and connection initialization, explicit `account/read` configuration reloads, and routing after ChatGPT sign-in, including propagation of configuration errors. GitOrigin-RevId: 8b90ee609ed24f8618cd21f160eb7001703b8bd1
This commit is contained in:
@@ -150,7 +150,9 @@ impl AccountRequestProcessor {
|
||||
.set_workspace_routing_resolver(Arc::downgrade(&resolver));
|
||||
let startup = processor.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = startup.read_account(/*request*/ None).await;
|
||||
let _ = startup
|
||||
.read_account_for_background_workspace_routing()
|
||||
.await;
|
||||
});
|
||||
processor
|
||||
}
|
||||
|
||||
@@ -128,6 +128,21 @@ impl WorkspaceRoutingResolver for AccountRequestProcessor {
|
||||
}
|
||||
|
||||
impl AccountRequestProcessor {
|
||||
pub(super) async fn read_account_for_background_workspace_routing(
|
||||
&self,
|
||||
) -> Result<Option<AccountRead>, AccountReadError> {
|
||||
if !self
|
||||
.auth_manager
|
||||
.auth_cached()
|
||||
.as_ref()
|
||||
.is_some_and(CodexAuth::is_chatgpt_auth)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.read_account(/*request*/ None).await.map(Some)
|
||||
}
|
||||
|
||||
pub(crate) fn notify_workspace_routing_to_connection(&self, connection_id: ConnectionId) {
|
||||
let processor = self.clone();
|
||||
let auth_changes = self.auth_manager.auth_change_state_receiver();
|
||||
@@ -136,7 +151,9 @@ impl AccountRequestProcessor {
|
||||
if auth_changes.borrow().owner_generation != owner_generation {
|
||||
return;
|
||||
}
|
||||
if let Ok(response) = processor.read_account(/*request*/ None).await
|
||||
if let Ok(Some(response)) = processor
|
||||
.read_account_for_background_workspace_routing()
|
||||
.await
|
||||
&& response.workspace_routing.is_some()
|
||||
&& auth_changes.borrow().owner_generation == owner_generation
|
||||
{
|
||||
|
||||
+127
-1
@@ -1,10 +1,136 @@
|
||||
//! Origin resolution and strict discovery validation.
|
||||
//! Origin resolution, strict discovery validation, and background read eligibility.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case(None; "signed_out")]
|
||||
#[test_case(Some("dummy"); "api_key")]
|
||||
#[tokio::test]
|
||||
async fn background_routing_skips_config_until_chatgpt_sign_in(
|
||||
api_key: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_login::AuthKeyringBackendKind;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
let home = tempfile::tempdir()?;
|
||||
let backend = wiremock::MockServer::start().await;
|
||||
app_test_support::mount_workspace_routing(&backend).await;
|
||||
let loads = Arc::new(AtomicUsize::new(/*v*/ 0));
|
||||
let calls = Arc::clone(&loads);
|
||||
let loader = CloudConfigBundleLoader::from_getter(move || {
|
||||
calls.fetch_add(/*val*/ 1, Ordering::SeqCst);
|
||||
async { Ok(None) }
|
||||
});
|
||||
let manager = ConfigManager::new_for_tests(
|
||||
home.path().to_path_buf(),
|
||||
vec![(
|
||||
"chatgpt_base_url".into(),
|
||||
format!("{}/backend-api", backend.uri()).into(),
|
||||
)],
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
loader,
|
||||
);
|
||||
let config = Arc::new(
|
||||
manager
|
||||
.load_latest_config(Some(home.path().to_path_buf()))
|
||||
.await?,
|
||||
);
|
||||
if let Some(api_key) = api_key {
|
||||
codex_login::login_with_api_key(
|
||||
home.path(),
|
||||
api_key,
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
)?;
|
||||
}
|
||||
let auth_manager = Arc::new(
|
||||
AuthManager::new(
|
||||
home.path().to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
Some(config.chatgpt_base_url.clone()),
|
||||
AuthKeyringBackendKind::default(),
|
||||
config.auth_config().auth_route_config,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
let threads = Arc::new(
|
||||
codex_core::test_support::thread_manager_with_models_provider_and_home(
|
||||
CodexAuth::from_api_key("dummy"),
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(EnvironmentManager::default_for_tests()),
|
||||
),
|
||||
);
|
||||
let (sender, _receiver) = tokio::sync::mpsc::channel(16);
|
||||
let outgoing = Arc::new(OutgoingMessageSender::new(
|
||||
sender,
|
||||
codex_analytics::AnalyticsEventsClient::disabled(),
|
||||
));
|
||||
let processor = AccountRequestProcessor::new(
|
||||
Arc::clone(&auth_manager),
|
||||
threads,
|
||||
outgoing,
|
||||
config,
|
||||
manager,
|
||||
);
|
||||
let initial_loads = loads.load(Ordering::SeqCst);
|
||||
for _ in 0..3 {
|
||||
assert!(
|
||||
processor
|
||||
.read_account_for_background_workspace_routing()
|
||||
.await?
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
assert_eq!(loads.load(Ordering::SeqCst), initial_loads);
|
||||
|
||||
processor.read_account(/*request*/ None).await?;
|
||||
assert!(loads.load(Ordering::SeqCst) > initial_loads);
|
||||
|
||||
write_chatgpt_auth(
|
||||
home.path(),
|
||||
ChatGptAuthFixture::new("access-chatgpt").account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
assert!(auth_manager.reload().await);
|
||||
let initial_loads = loads.load(Ordering::SeqCst);
|
||||
let read = processor
|
||||
.read_account_for_background_workspace_routing()
|
||||
.await?
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
read.workspace_routing,
|
||||
Some(WorkspaceRouting {
|
||||
chatgpt_account_id: "account-123".into(),
|
||||
backend_origin: "https://chatgpt.com".into(),
|
||||
account_routing_override: "NO_CONSTRAINT".into(),
|
||||
})
|
||||
);
|
||||
assert!(loads.load(Ordering::SeqCst) > initial_loads);
|
||||
|
||||
std::fs::write(home.path().join("config.toml"), "invalid = [")?;
|
||||
assert!(matches!(
|
||||
processor
|
||||
.read_account_for_background_workspace_routing()
|
||||
.await,
|
||||
Err(AccountReadError::Routing(
|
||||
WorkspaceRoutingError::RequirementsLoad
|
||||
))
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Some("https://gov.chatgpt.com/backend-api/"), "NO_CONSTRAINT", "https://gov.chatgpt.com"; "configured_only")]
|
||||
#[test_case(None, "https://gov.chatgpt.com", "https://gov.chatgpt.com"; "discovered_only")]
|
||||
#[test_case(Some("https://GOV.chatgpt.com:443/backend-api/"), "https://gov.chatgpt.com", "https://gov.chatgpt.com"; "matching_effective_port")]
|
||||
|
||||
Reference in New Issue
Block a user