diff --git a/codex-rs/core/src/external_agent_config.rs b/codex-rs/core/src/external_agent_config.rs index b39fba7c87..a688fce12a 100644 --- a/codex-rs/core/src/external_agent_config.rs +++ b/codex-rs/core/src/external_agent_config.rs @@ -3,6 +3,7 @@ use crate::plugins::PluginId; use crate::plugins::PluginInstallRequest; use crate::plugins::PluginsManager; use crate::plugins::add_marketplace; +use crate::plugins::validate_marketplace_add_source; use codex_utils_absolute_path::AbsolutePathBuf; use serde_json::Value as JsonValue; use std::collections::BTreeMap; @@ -536,6 +537,14 @@ fn extract_plugin_migration_details( settings: &JsonValue, configured_enabled_plugins: &HashSet, ) -> Option { + let loadable_marketplaces = collect_marketplace_import_sources(settings) + .into_iter() + .filter_map(|(marketplace_name, source)| { + validate_marketplace_add_source(&source.source, source.ref_name) + .ok() + .map(|()| marketplace_name) + }) + .collect::>(); let mut plugins = BTreeMap::new(); for plugin_id in collect_enabled_plugins(settings) .into_iter() @@ -544,6 +553,9 @@ fn extract_plugin_migration_details( let Ok(plugin_id) = PluginId::parse(&plugin_id) else { continue; }; + if !loadable_marketplaces.contains(&plugin_id.marketplace_name) { + continue; + } let plugin_group = plugins .entry(plugin_id.marketplace_name.clone()) .or_insert_with(|| PluginsMigration { diff --git a/codex-rs/core/src/external_agent_config_tests.rs b/codex-rs/core/src/external_agent_config_tests.rs index c343688560..bdcf21ed95 100644 --- a/codex-rs/core/src/external_agent_config_tests.rs +++ b/codex-rs/core/src/external_agent_config_tests.rs @@ -418,6 +418,11 @@ fn detect_home_lists_enabled_plugins_from_settings() { "formatter@acme-tools": true, "deployer@acme-tools": true, "analyzer@security-plugins": false + }, + "extraKnownMarketplaces": { + "acme-tools": { + "source": "acme-corp/claude-plugins" + } } }"#, ) @@ -464,6 +469,11 @@ fn detect_repo_skips_plugins_that_are_already_enabled_in_codex() { "enabledPlugins": { "formatter@acme-tools": true, "deployer@acme-tools": true + }, + "extraKnownMarketplaces": { + "acme-tools": { + "source": "acme-corp/claude-plugins" + } } }"#, ) @@ -516,6 +526,59 @@ async fn import_plugins_requires_details() { assert_eq!(err.to_string(), "plugins migration item is missing details"); } +#[test] +fn detect_home_skips_plugins_without_marketplace_source() { + let (_root, claude_home, codex_home) = fixture_paths(); + fs::create_dir_all(&claude_home).expect("create claude home"); + fs::write( + claude_home.join("settings.json"), + r#"{ + "enabledPlugins": { + "formatter@acme-tools": true + } + }"#, + ) + .expect("write settings"); + + let items = service_for_paths(claude_home, codex_home) + .detect(ExternalAgentConfigDetectOptions { + include_home: true, + cwds: None, + }) + .expect("detect"); + + assert_eq!(items, Vec::::new()); +} + +#[test] +fn detect_home_skips_plugins_with_invalid_marketplace_source() { + let (_root, claude_home, codex_home) = fixture_paths(); + fs::create_dir_all(&claude_home).expect("create claude home"); + fs::write( + claude_home.join("settings.json"), + r#"{ + "enabledPlugins": { + "formatter@acme-tools": true + }, + "extraKnownMarketplaces": { + "acme-tools": { + "source": "github" + } + } + }"#, + ) + .expect("write settings"); + + let items = service_for_paths(claude_home, codex_home) + .detect(ExternalAgentConfigDetectOptions { + include_home: true, + cwds: None, + }) + .expect("detect"); + + assert_eq!(items, Vec::::new()); +} + #[tokio::test] async fn import_plugins_requires_source_marketplace_details() { let (_root, claude_home, codex_home) = fixture_paths(); diff --git a/codex-rs/core/src/plugins/marketplace_add.rs b/codex-rs/core/src/plugins/marketplace_add.rs index 49c1c2e565..2ef11106f8 100644 --- a/codex-rs/core/src/plugins/marketplace_add.rs +++ b/codex-rs/core/src/plugins/marketplace_add.rs @@ -56,6 +56,13 @@ pub async fn add_marketplace( .map_err(|err| MarketplaceAddError::Internal(format!("failed to add marketplace: {err}")))? } +pub fn validate_marketplace_add_source( + source: &str, + ref_name: Option, +) -> Result<(), MarketplaceAddError> { + parse_marketplace_source(source, ref_name).map(|_| ()) +} + fn add_marketplace_sync( codex_home: &Path, request: MarketplaceAddRequest, diff --git a/codex-rs/core/src/plugins/mod.rs b/codex-rs/core/src/plugins/mod.rs index 7ffb80b449..5b46109bce 100644 --- a/codex-rs/core/src/plugins/mod.rs +++ b/codex-rs/core/src/plugins/mod.rs @@ -63,6 +63,7 @@ pub use marketplace_add::MarketplaceAddError; pub use marketplace_add::MarketplaceAddOutcome; pub use marketplace_add::MarketplaceAddRequest; pub use marketplace_add::add_marketplace; +pub use marketplace_add::validate_marketplace_add_source; pub use remote::RemotePluginFetchError; pub use remote::fetch_remote_featured_plugin_ids; pub(crate) use render::render_explicit_plugin_instructions;