diff --git a/codex-rs/config/src/loader/mod.rs b/codex-rs/config/src/loader/mod.rs index 6e1dd082fa..2ab8e45c46 100644 --- a/codex-rs/config/src/loader/mod.rs +++ b/codex-rs/config/src/loader/mod.rs @@ -506,6 +506,21 @@ pub async fn load_config_layers_state( ignore_user_and_project_exec_policy_rules, ); config_layer_stack.is_projectless = is_projectless; + if config_layer_stack.layers_low_to_high().any(|layer| { + matches!( + layer.name, + ConfigLayerSource::User { .. } | ConfigLayerSource::Project { .. } + ) && layer + .config + .get("features") + .and_then(|features| features.get("multi_agent")) + .and_then(TomlValue::as_bool) + .is_some() + }) { + startup_warnings.push( + "`[features].multi_agent` is deprecated. It still controls the V1 fallback. `[agents].enabled = false` disables subagent tools even when the model selects a subagent version, unless `[features].multi_agent_v2` is enabled.".to_string(), + ); + } startup_warnings.extend(ignored_config_warning( &config_layer_stack, &requirements_layers, diff --git a/codex-rs/config/src/loader/tests.rs b/codex-rs/config/src/loader/tests.rs index e38074b812..840882ac43 100644 --- a/codex-rs/config/src/loader/tests.rs +++ b/codex-rs/config/src/loader/tests.rs @@ -718,6 +718,63 @@ async fn missing_packaged_defaults_file_returns_an_error() { ); } +#[tokio::test] +async fn multi_agent_warns_for_explicit_user_and_project_settings() -> std::io::Result<()> { + let home = tempdir()?; + let workspace = tempdir()?; + let cwd = AbsolutePathBuf::try_from(workspace.path())?; + let trust_key = TomlValue::String(project_trust_key(workspace.path())); + let project_config_dir = workspace.path().join(".codex"); + std::fs::create_dir(&project_config_dir)?; + let session_flags = [( + "features.multi_agent".to_string(), + TomlValue::Boolean(false), + )]; + for (user, project, flags, warn) in [ + ("", "", session_flags.as_slice(), false), + ("[features]\nmulti_agent = false", "", &[], true), + ( + "", + "[features]\nmulti_agent = true", + session_flags.as_slice(), + true, + ), + ] { + std::fs::write( + home.path().join(CONFIG_TOML_FILE), + format!("[projects.{trust_key}]\ntrust_level = 'trusted'\n{user}"), + )?; + std::fs::write(project_config_dir.join(CONFIG_TOML_FILE), project)?; + let stack = load_config_layers_state( + &TestFileSystem, + home.path(), + Some(cwd.clone()), + flags, + LoaderOverrides::without_managed_config_for_tests(), + &crate::NoopThreadConfigLoader, + ) + .await?; + let warned = stack + .startup_warnings() + .unwrap_or_default() + .iter() + .any(|warning| { + warning.contains( + "`[features].multi_agent` is deprecated. It still controls the V1 fallback.", + ) + }); + assert_eq!( + ( + stack.effective_config()["features"]["multi_agent"].as_bool(), + warned + ), + (Some(false), warn), + "user={user:?}, project={project:?}" + ); + } + Ok(()) +} + #[cfg(windows)] #[tokio::test] async fn default_windows_managed_config_is_ignored_with_warning() { diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index f76315c675..65a43d8157 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -1630,7 +1630,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::Collab, key: "multi_agent", - stage: Stage::Stable, + stage: Stage::Deprecated, default_enabled: true, }, FeatureSpec { diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 67d4e8b325..f841737d8e 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -142,13 +142,16 @@ fn codex_apps_mcp_protocol_can_be_enabled_independently_of_generic_mcp() { } #[test] -fn default_enabled_features_are_stable() { +fn default_enabled_features_have_shipped() { for spec in crate::FEATURES { // Instant interrupts are enabled by default while retaining their development stage. if spec.default_enabled && spec.id != Feature::InstantInterrupt { assert!( - matches!(spec.stage, Stage::Stable | Stage::Removed), - "feature `{}` is enabled by default but is not stable/removed ({:?})", + matches!( + spec.stage, + Stage::Stable | Stage::Deprecated | Stage::Removed + ), + "feature `{}` is enabled by default at stage {:?}", spec.key, spec.stage );