[codex] Ignore local curated plugins when remote catalog is active (#29765)

## Summary

- suppress configured `openai-curated` plugins when the remote plugin
feature is enabled and auth uses the Codex backend
- preserve `openai-api-curated` and non-Codex-backend behavior while
including remote catalog activation in the plugin load cache key
- add core plugin coverage and an app-server integration test for
runtime feature enablement

## Why

The Codex app enables remote plugins through process-local runtime
feature enablement, which can happen after app-server startup tasks have
already observed legacy local plugin state. The existing conflict logic
only preferred a remote plugin when the same plugin was already
installed remotely, so a configured legacy-only plugin could continue
exposing skills and other capabilities from `openai-curated`.

## Impact

When the remote catalog is active, legacy `openai-curated` plugins no
longer contribute skills, MCP servers, apps, or hooks. Remote installed
plugins continue to load normally, and `openai-api-curated` remains
unaffected. This does not change remote fetch, bundle sync, or uninstall
behavior.

## Validation

- `just test -p codex-core-plugins
remote_global_catalog_ignores_local_curated_plugins
remote_plugin_feature_keeps_local_curated_without_codex_backend`
- `just test -p codex-app-server
runtime_remote_plugin_enablement_excludes_local_curated_plugin_skills`
- `just fmt`
- `git diff --check`
This commit is contained in:
xl-openai
2026-06-23 19:51:31 -07:00
committed by GitHub
parent 2696e7199b
commit ff78e21215
5 changed files with 189 additions and 30 deletions
+14 -8
View File
@@ -384,15 +384,15 @@ struct LoadedPluginsCache {
struct PluginLoadCacheKey {
configured_plugins: HashMap<String, PluginConfig>,
skill_config_rules: SkillConfigRules,
remote_plugin_enabled: bool,
remote_global_catalog_active: bool,
}
impl PluginLoadCacheKey {
fn from_config(config: &PluginsConfigInput) -> Self {
fn from_config(config: &PluginsConfigInput, remote_global_catalog_active: bool) -> Self {
Self {
configured_plugins: configured_plugins_from_stack(&config.config_layer_stack),
skill_config_rules: skill_config_rules_from_stack(&config.config_layer_stack),
remote_plugin_enabled: config.remote_plugin_enabled,
remote_global_catalog_active,
}
}
}
@@ -459,6 +459,10 @@ impl PluginsManager {
}
}
fn remote_global_catalog_active(&self, config: &PluginsConfigInput) -> bool {
config.remote_plugin_enabled && self.auth_mode().is_some_and(AuthMode::uses_codex_backend)
}
pub fn set_analytics_events_client(&self, analytics_events_client: AnalyticsEventsClient) {
let mut stored_client = match self.analytics_events_client.write() {
Ok(client_guard) => client_guard,
@@ -490,7 +494,8 @@ impl PluginsManager {
if !config.plugins_enabled {
return None;
}
let key = PluginLoadCacheKey::from_config(config);
let key =
PluginLoadCacheKey::from_config(config, self.remote_global_catalog_active(config));
self.loaded_plugins_cache
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
@@ -519,7 +524,8 @@ impl PluginsManager {
return PluginLoadOutcome::default();
}
let cache_key = PluginLoadCacheKey::from_config(config);
let remote_global_catalog_active = self.remote_global_catalog_active(config);
let cache_key = PluginLoadCacheKey::from_config(config, remote_global_catalog_active);
if !force_reload && let Some(plugins) = self.cached_loaded_plugins(&cache_key) {
return self.resolve_loaded_plugins_for_auth(plugins);
}
@@ -539,7 +545,7 @@ impl PluginsManager {
&self.store,
Some(&plugin_skill_snapshots),
self.restriction_product,
config.remote_plugin_enabled,
remote_global_catalog_active,
)
.await;
log_plugin_load_errors(&plugins);
@@ -624,7 +630,7 @@ impl PluginsManager {
&self.store,
/*plugin_skill_snapshots*/ None,
self.restriction_product,
config.remote_plugin_enabled,
self.remote_global_catalog_active(config),
)
.await;
self.resolve_loaded_plugins_for_auth(plugins)
@@ -643,7 +649,7 @@ impl PluginsManager {
config_layer_stack,
self.remote_installed_plugin_configs(),
&self.store,
config.remote_plugin_enabled,
self.remote_global_catalog_active(config),
)
.await
}