diff --git a/codex-rs/utils/plugins/src/plugin_namespace.rs b/codex-rs/utils/plugins/src/plugin_namespace.rs index a485ab0013ca..db1b9ce5328b 100644 --- a/codex-rs/utils/plugins/src/plugin_namespace.rs +++ b/codex-rs/utils/plugins/src/plugin_namespace.rs @@ -39,28 +39,27 @@ pub fn agent_plugin_schema_status(contents: &str) -> AgentPluginSchemaStatus { pub fn find_plugin_manifest_path(plugin_root: &Path) -> Option { let agent_manifest_path = plugin_root.join(AGENT_PLUGIN_MANIFEST_RELATIVE_PATH); - match std::fs::symlink_metadata(&agent_manifest_path) { + let agent_plugin_manifest = match std::fs::symlink_metadata(&agent_manifest_path) { Ok(metadata) if metadata.file_type().is_symlink() || !metadata.file_type().is_file() => { return None; } - Ok(_) => { - if std::fs::read_to_string(&agent_manifest_path) - .ok() - .is_some_and(|contents| { - agent_plugin_schema_status(&contents) != AgentPluginSchemaStatus::Unrelated - }) - { - return Some(agent_manifest_path); - } - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} + Ok(_) => std::fs::read_to_string(&agent_manifest_path) + .ok() + .is_some_and(|contents| { + agent_plugin_schema_status(&contents) != AgentPluginSchemaStatus::Unrelated + }) + .then_some(agent_manifest_path), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, Err(_) => return None, - } + }; + // Prefer a Codex-side manifest over the Agent Plugin spec: the spec can't yet + // forward env vars to MCP servers (openai/codex#38438). Revert at parity. DISCOVERABLE_PLUGIN_MANIFEST_PATHS .iter() .map(|relative_path| plugin_root.join(relative_path)) .find(|manifest_path| manifest_path.is_file()) + .or(agent_plugin_manifest) } #[derive(serde::Deserialize)] @@ -203,6 +202,22 @@ mod tests { assert_eq!(find_plugin_manifest_path(&plugin_root), Some(manifest_path)); } + #[test] + fn prefers_codex_plugin_over_schema_declared_root_manifest() { + let tmp = tempdir().expect("tempdir"); + let plugin_root = tmp.path().join("plugins/portable"); + let codex_path = plugin_root.join(".codex-plugin/plugin.json"); + fs::create_dir_all(codex_path.parent().expect("parent")).expect("mkdir"); + fs::write( + plugin_root.join(AGENT_PLUGIN_MANIFEST_RELATIVE_PATH), + format!(r#"{{"$schema":"{AGENT_PLUGIN_SCHEMA_URI}","name":"portable"}}"#), + ) + .expect("write spec manifest"); + fs::write(&codex_path, r#"{"name":"portable"}"#).expect("write codex manifest"); + + assert_eq!(find_plugin_manifest_path(&plugin_root), Some(codex_path)); + } + #[test] fn ignores_unrelated_root_plugin_manifest_before_legacy_fallback() { let tmp = tempdir().expect("tempdir");