From e69dab677a73726b354af5ff7cbfb741d6b2f60e Mon Sep 17 00:00:00 2001 From: malsamiri Date: Tue, 7 Jul 2026 15:41:48 -0700 Subject: [PATCH] Scope Codex Apps cache by plugin connectors --- codex-rs/codex-mcp/src/codex_apps_cache.rs | 18 +++++++-- .../codex-mcp/src/codex_apps_cache_tests.rs | 37 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/codex-rs/codex-mcp/src/codex_apps_cache.rs b/codex-rs/codex-mcp/src/codex_apps_cache.rs index 61f179a13cb..fc6f0d51b9d 100644 --- a/codex-rs/codex-mcp/src/codex_apps_cache.rs +++ b/codex-rs/codex-mcp/src/codex_apps_cache.rs @@ -78,7 +78,7 @@ impl CodexAppsToolsCacheContext { pub(crate) fn server_info_cache_path(&self) -> PathBuf { self.entry .identity - .cache_path_in(CODEX_APPS_SERVER_INFO_CACHE_DIR) + .auth_cache_path_in(CODEX_APPS_SERVER_INFO_CACHE_DIR) } pub(crate) fn current_tools(&self) -> Option> { @@ -156,11 +156,12 @@ impl CodexAppsToolsCache { let mut expected_connector_ids = expected_connector_ids.into_iter().collect::>(); expected_connector_ids.sort_unstable(); expected_connector_ids.dedup(); - let expected_connector_ids = Arc::new(expected_connector_ids); let identity = CodexAppsToolsCacheIdentity { codex_home, auth_key, + expected_connector_ids: expected_connector_ids.clone(), }; + let expected_connector_ids = Arc::new(expected_connector_ids); let mut entries = lock_unpoisoned(&self.entries); let entry = entries .entry(identity.clone()) @@ -214,12 +215,14 @@ impl CodexAppsToolsCacheEntry { /// Everything that decides whether two Codex Apps clients can share tools. /// -/// The auth key says whose catalog we are reading. `codex_home` keeps the +/// The auth key says whose catalog we are reading. Expected connector IDs keep +/// plugin contexts from sharing a raw snapshot. `codex_home` keeps the /// persisted cache under the right home directory. #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct CodexAppsToolsCacheIdentity { codex_home: PathBuf, auth_key: CodexAppsToolsCacheKey, + expected_connector_ids: Vec, } impl CodexAppsToolsCacheIdentity { @@ -227,6 +230,15 @@ impl CodexAppsToolsCacheIdentity { // `codex_home` is already the parent directory. Keep it out of the // filename hash so non-UTF-8 Unix paths cannot collapse distinct auth // keys onto the same disk cache file. + let identity_json = serde_json::to_string(&(&self.auth_key, &self.expected_connector_ids)) + .unwrap_or_default(); + let identity_hash = sha1_hex(&identity_json); + self.codex_home + .join(cache_dir) + .join(format!("{identity_hash}.json")) + } + + fn auth_cache_path_in(&self, cache_dir: &str) -> PathBuf { let identity_json = serde_json::to_string(&self.auth_key).unwrap_or_default(); let identity_hash = sha1_hex(&identity_json); self.codex_home diff --git a/codex-rs/codex-mcp/src/codex_apps_cache_tests.rs b/codex-rs/codex-mcp/src/codex_apps_cache_tests.rs index 8beb1197ced..298be636152 100644 --- a/codex-rs/codex-mcp/src/codex_apps_cache_tests.rs +++ b/codex-rs/codex-mcp/src/codex_apps_cache_tests.rs @@ -405,6 +405,43 @@ fn codex_apps_tools_cache_publishes_newest_shared_snapshot() { ); } +#[test] +fn codex_apps_tools_cache_scopes_plugin_connector_expectations() { + let codex_home = tempdir().expect("tempdir"); + let cache = CodexAppsToolsCache::default(); + let auth_key = CodexAppsToolsCacheKey { + account_id: Some("account-one".to_string()), + chatgpt_user_id: Some("user-one".to_string()), + is_workspace_account: false, + }; + let connector_a = cache.context( + codex_home.path().to_path_buf(), + auth_key.clone(), + ["connector_a".to_string()], + ); + let connector_b = cache.context( + codex_home.path().to_path_buf(), + auth_key, + ["connector_b".to_string()], + ); + + connector_a.store_current_tools_for_test(vec![create_test_tool( + CODEX_APPS_MCP_SERVER_NAME, + "connector_a_search", + )]); + + assert!(connector_a.current_tools().is_some()); + assert!(connector_b.current_tools().is_none()); + assert_ne!( + connector_a.tools_cache_path(), + connector_b.tools_cache_path() + ); + assert_eq!( + connector_a.server_info_cache_path(), + connector_b.server_info_cache_path() + ); +} + #[test] fn codex_apps_tools_cache_keeps_live_publish_when_disk_persistence_fails() { let codex_home = tempdir().expect("tempdir");