From 3ad642e520f76ccec0cbb5964ce605629751ebff Mon Sep 17 00:00:00 2001 From: Alex Zamoshchin Date: Mon, 26 Oct 2026 14:04:24 +0000 Subject: [PATCH] Allow disabling plugin discovery prefetch at app-server startup (#57249) ## Why Hosts that only need installed plugin inventory should be able to skip startup requests for plugin discovery. ## What changed Add the stable, default-enabled `features.plugin_discovery_prefetch` flag. Setting it to `false` skips startup prefetching of the plugin directory, featured plugins, and recommendations while preserving installed plugin cache refreshes and bundle downloads. ## Testing Update app-server integration coverage to verify that installed plugin bundles download with discovery prefetch disabled and that no directory, featured, or suggested plugin requests are made during startup. Also cover listing remote marketplaces with the flag disabled. GitOrigin-RevId: 7cc3072f6045131b9f1acb851f6240db75ac173e --- codex-rs/app-server/src/message_processor.rs | 2 ++ .../request_processors/account_processor.rs | 1 + .../app-server/tests/suite/v2/plugin_list.rs | 18 +++++++++++++++-- codex-rs/core-plugins/src/manager.rs | 20 +++++++++++-------- codex-rs/core-plugins/src/manager_tests.rs | 1 + codex-rs/core/config.schema.json | 6 ++++++ codex-rs/features/src/lib.rs | 9 +++++++++ 7 files changed, 47 insertions(+), 10 deletions(-) diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 3162839f08..14ef60000b 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -83,6 +83,7 @@ use codex_core::config::Config; use codex_core::config::ThreadStoreConfig; use codex_exec_server::EnvironmentManager; use codex_extension_api::TurnStartAdmission; +use codex_features::Feature; use codex_feedback::CodexFeedback; use codex_goal_extension::GoalService; use codex_home::CodexHomeUserInstructionsProvider; @@ -568,6 +569,7 @@ impl MessageProcessor { .plugins_manager() .maybe_start_plugin_startup_tasks_for_config( &config.plugins_config_input(), + config.features.enabled(Feature::PluginDiscoveryPrefetch), reload_config, Some(on_effective_plugins_changed), startup_warnings_tx, diff --git a/codex-rs/app-server/src/request_processors/account_processor.rs b/codex-rs/app-server/src/request_processors/account_processor.rs index 90b5ef4af9..cf4c6406bc 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -308,6 +308,7 @@ impl AccountRequestProcessor { .maybe_start_remote_plugin_caches_refresh( &plugins_config, auth, + /*prefetch_recommendations*/ true, Some(on_effective_plugins_changed), ); } diff --git a/codex-rs/app-server/tests/suite/v2/plugin_list.rs b/codex-rs/app-server/tests/suite/v2/plugin_list.rs index c03c422818..956cb5d652 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -1961,7 +1961,8 @@ enabled = true } #[tokio::test] -async fn app_server_startup_sync_downloads_remote_installed_plugin_bundles() -> Result<()> { +async fn app_server_startup_downloads_installed_plugin_bundles_without_discovery_prefetch() +-> Result<()> { let codex_home = TempDir::new()?; let server = MockServer::start().await; write_remote_plugin_catalog_config( @@ -2001,11 +2002,24 @@ async fn app_server_startup_sync_downloads_remote_installed_plugin_bundles() -> .await; mount_empty_user_installed_plugins(&server).await; + for endpoint in [ + "/backend-api/ps/plugins/list", + "/backend-api/plugins/featured", + "/backend-api/ps/plugins/suggested/codex", + ] { + Mock::given(path(endpoint)) + .respond_with(ResponseTemplate::new(404)) + .expect(0) + .mount(&server) + .await; + } + let installed_path = codex_home .path() .join("plugins/cache/openai-curated-remote/linear/1.2.3"); let _mcp = TestAppServer::builder() .with_codex_home(codex_home.path()) + .with_args(&["-c", "features.plugin_discovery_prefetch=false"]) .with_plugin_startup_tasks() .with_env_overrides(&[(TEST_ALLOW_HTTP_REMOTE_PLUGIN_BUNDLE_DOWNLOADS, Some("1"))]) .build_initialized_with_timeout(DEFAULT_TIMEOUT) @@ -2137,7 +2151,7 @@ enum ProjectPluginConfiguration { Invalid, } -#[test_case(ProjectPluginConfiguration::None, None, "", true, true; "without project override")] +#[test_case(ProjectPluginConfiguration::None, None, "plugin_discovery_prefetch = false", true, true; "without discovery prefetch")] #[test_case(ProjectPluginConfiguration::Disabled, None, "", true, true; "project disables local plugins")] #[test_case(ProjectPluginConfiguration::Invalid, None, "", true, true; "invalid project preserves remote plugins")] #[test_case(ProjectPluginConfiguration::None, Some("tpp"), "", true, true; "configured product sku")] diff --git a/codex-rs/core-plugins/src/manager.rs b/codex-rs/core-plugins/src/manager.rs index 6d9d676258..4c299c32fc 100644 --- a/codex-rs/core-plugins/src/manager.rs +++ b/codex-rs/core-plugins/src/manager.rs @@ -1588,6 +1588,7 @@ impl PluginsManager { self: &Arc, config: &PluginsConfigInput, auth: Option, + prefetch_recommendations: bool, on_effective_plugins_changed: Option, ) { self.maybe_start_remote_installed_plugins_cache_refresh_with_notify( @@ -1598,6 +1599,9 @@ impl PluginsManager { EffectivePluginsChange::default(), ); + if !prefetch_recommendations { + return; + } let manager = Arc::clone(self); let config = config.clone(); tokio::spawn(async move { @@ -1873,6 +1877,7 @@ impl PluginsManager { self.maybe_start_remote_plugin_caches_refresh( &context.global_config, auth.clone(), + /*prefetch_recommendations*/ true, on_effective_plugins_changed.clone(), ); self.maybe_start_remote_installed_plugin_bundle_sync( @@ -2874,6 +2879,7 @@ impl PluginsManager { pub fn maybe_start_plugin_startup_tasks_for_config( self: &Arc, config: &PluginsConfigInput, + prefetch_discovery: bool, reload_config: ConfigLayerReload, on_effective_plugins_changed: Option, upgrade_warnings: mpsc::UnboundedSender, @@ -2966,6 +2972,7 @@ impl PluginsManager { manager.maybe_start_remote_plugin_caches_refresh( &config_for_remote_sync, auth.clone(), + prefetch_discovery, on_effective_plugins_changed.clone(), ); manager.maybe_start_remote_installed_plugin_bundle_sync( @@ -2973,6 +2980,9 @@ impl PluginsManager { auth.clone(), on_effective_plugins_changed, ); + if !prefetch_discovery { + return; + } let mut scopes = crate::remote::cached_remote_plugin_catalog_scopes( manager.codex_home.as_path(), &remote_plugin_service_config(&config_for_remote_sync), @@ -2985,18 +2995,12 @@ impl PluginsManager { } manager.maybe_start_remote_catalog_cache_refresh( &config_for_remote_sync, - auth, + auth.clone(), scopes, RemoteCatalogCacheRefreshMode::Force, ); - }); - - let config_for_featured_plugins = config.clone(); - let manager = Arc::clone(self); - tokio::spawn(async move { - let auth = manager.auth_manager.auth().await; if let Err(err) = manager - .featured_plugin_ids_for_config(&config_for_featured_plugins, auth.as_ref()) + .featured_plugin_ids_for_config(&config_for_remote_sync, auth.as_ref()) .await { warn!( diff --git a/codex-rs/core-plugins/src/manager_tests.rs b/codex-rs/core-plugins/src/manager_tests.rs index 39ac515871..7439f7b038 100644 --- a/codex-rs/core-plugins/src/manager_tests.rs +++ b/codex-rs/core-plugins/src/manager_tests.rs @@ -6147,6 +6147,7 @@ plugins = true manager.maybe_start_remote_plugin_caches_refresh( &config, Some(auth.clone()), + /*prefetch_recommendations*/ true, /*on_effective_plugins_changed*/ None, ); diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index d15af0114b..cc12557fcb 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1197,6 +1197,9 @@ "personality": { "type": "boolean" }, + "plugin_discovery_prefetch": { + "type": "boolean" + }, "plugin_hooks": { "type": "boolean" }, @@ -7818,6 +7821,9 @@ "personality": { "type": "boolean" }, + "plugin_discovery_prefetch": { + "type": "boolean" + }, "plugin_hooks": { "type": "boolean" }, diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 83daf42efc..1fdbb4bf5a 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -351,6 +351,9 @@ pub enum Feature { ComputerUse, /// Enable the PS-backed remote plugin catalog. RemotePlugin, + /// Prefetch the plugin directory, featured plugins, and recommendations at app-server startup. + /// Inventory-only hosts can disable this. Legacy suggestions read the cached directory. + PluginDiscoveryPrefetch, /// Enable remote plugin sharing flows. PluginSharing, /// Removed compatibility flag retained as a no-op. @@ -1816,6 +1819,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Stable, default_enabled: true, }, + FeatureSpec { + id: Feature::PluginDiscoveryPrefetch, + key: "plugin_discovery_prefetch", + stage: Stage::Stable, + default_enabled: true, + }, FeatureSpec { id: Feature::PluginSharing, key: "plugin_sharing",