From 955a37d5dca85bc7836c33176ba08c489d5003ee Mon Sep 17 00:00:00 2001 From: robhanlon-oai <227463433+robhanlon-oai@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:34:28 -0800 Subject: [PATCH] Support clearing map values via --config overrides --- codex-rs/config/src/merge.rs | 77 ++++++++++++++++++++++++++++++++ codex-rs/config/src/overrides.rs | 37 +++++++++++++++ docs/config.md | 12 +++++ 3 files changed, 126 insertions(+) diff --git a/codex-rs/config/src/merge.rs b/codex-rs/config/src/merge.rs index eacbbcb788..fe777c6edc 100644 --- a/codex-rs/config/src/merge.rs +++ b/codex-rs/config/src/merge.rs @@ -5,6 +5,11 @@ pub fn merge_toml_values(base: &mut TomlValue, overlay: &TomlValue) { if let TomlValue::Table(overlay_table) = overlay && let TomlValue::Table(base_table) = base { + if overlay_table.is_empty() { + base_table.clear(); + return; + } + for (key, value) in overlay_table { if let Some(existing) = base_table.get_mut(key) { merge_toml_values(existing, value); @@ -16,3 +21,75 @@ pub fn merge_toml_values(base: &mut TomlValue, overlay: &TomlValue) { *base = overlay.clone(); } } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use toml::toml; + + #[test] + fn empty_overlay_table_replaces_existing_table() { + let mut base = TomlValue::Table(toml! { + [mcp_servers.docs] + command = "uvx" + + [mcp_servers.logs] + command = "tail" + }); + let overlay = TomlValue::Table(toml! { + mcp_servers = {} + }); + + merge_toml_values(&mut base, &overlay); + + assert_eq!(base, TomlValue::Table(toml! { mcp_servers = {} })); + } + + #[test] + fn empty_overlay_table_only_clears_targeted_nested_table() { + let mut base = TomlValue::Table(toml! { + [model_providers.default] + name = "provider-a" + + [model_providers.default.extra] + endpoint = "https://example-a" + + [model_providers.other] + name = "provider-b" + }); + let overlay = TomlValue::Table(toml! { + model_providers = { default = {} } + }); + + merge_toml_values(&mut base, &overlay); + + assert_eq!( + base, + TomlValue::Table(toml! { + model_providers = { default = {}, other = { name = "provider-b" } } + }) + ); + } + + #[test] + fn non_empty_overlay_table_keeps_recursive_merge_behavior() { + let mut base = TomlValue::Table(toml! { + [mcp_servers.docs] + command = "uvx" + disabled = false + }); + let overlay = TomlValue::Table(toml! { + mcp_servers = { docs = { disabled = true } } + }); + + merge_toml_values(&mut base, &overlay); + + assert_eq!( + base, + TomlValue::Table(toml! { + mcp_servers = { docs = { command = "uvx", disabled = true } } + }) + ); + } +} diff --git a/codex-rs/config/src/overrides.rs b/codex-rs/config/src/overrides.rs index a27caedb9a..f5a1e0cab8 100644 --- a/codex-rs/config/src/overrides.rs +++ b/codex-rs/config/src/overrides.rs @@ -53,3 +53,40 @@ fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::merge::merge_toml_values; + use pretty_assertions::assert_eq; + use toml::toml; + + #[test] + fn build_cli_overrides_layer_preserves_empty_table_values() { + let cli_overrides = vec![( + "mcp_servers".to_string(), + TomlValue::Table(toml::map::Map::new()), + )]; + + let layer = build_cli_overrides_layer(&cli_overrides); + + assert_eq!(layer, TomlValue::Table(toml! { mcp_servers = {} })); + } + + #[test] + fn empty_table_cli_override_clears_existing_map() { + let cli_overrides = vec![( + "mcp_servers".to_string(), + TomlValue::Table(toml::map::Map::new()), + )]; + let cli_layer = build_cli_overrides_layer(&cli_overrides); + let mut merged = TomlValue::Table(toml! { + [mcp_servers.docs] + command = "uvx" + }); + + merge_toml_values(&mut merged, &cli_layer); + + assert_eq!(merged, TomlValue::Table(toml! { mcp_servers = {} })); + } +} diff --git a/docs/config.md b/docs/config.md index 9d53538cae..09d770b5a2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -12,6 +12,18 @@ Codex can connect to MCP servers configured in `~/.codex/config.toml`. See the c - https://developers.openai.com/codex/config-reference +### Clearing map-valued settings from the CLI + +`--config` / `-c` overrides can clear map-valued settings by assigning an explicit empty table. + +For example, to disable all configured MCP servers for a single run: + +```bash +codex --config 'mcp_servers={}' +``` + +This replacement behavior is generic, so any map-valued config key can be reset the same way. + ## Apps (Connectors) Use `$` in the composer to insert a ChatGPT connector; the popover lists accessible