config: add strict config parsing

This commit is contained in:
Michael Bolin
2026-05-04 09:41:50 -07:00
parent 33b19bcfde
commit b909ad9f3a
42 changed files with 1192 additions and 178 deletions
+24 -3
View File
@@ -190,6 +190,7 @@ async fn run_command_under_sandbox(
.map_err(anyhow::Error::msg)?,
codex_linux_sandbox_exe,
config_options,
config_overrides.strict_config,
)
.await?;
@@ -629,12 +630,14 @@ async fn load_debug_sandbox_config(
cli_overrides: Vec<(String, TomlValue)>,
codex_linux_sandbox_exe: Option<PathBuf>,
options: DebugSandboxConfigOptions,
strict_config: bool,
) -> anyhow::Result<Config> {
load_debug_sandbox_config_with_codex_home(
cli_overrides,
codex_linux_sandbox_exe,
options,
/*codex_home*/ None,
strict_config,
)
.await
}
@@ -644,6 +647,7 @@ async fn load_debug_sandbox_config_with_codex_home(
codex_linux_sandbox_exe: Option<PathBuf>,
options: DebugSandboxConfigOptions,
codex_home: Option<PathBuf>,
strict_config: bool,
) -> anyhow::Result<Config> {
let DebugSandboxConfigOptions {
permissions_profile,
@@ -673,6 +677,7 @@ async fn load_debug_sandbox_config_with_codex_home(
},
codex_home.clone(),
managed_requirements_mode,
strict_config,
)
.await?;
@@ -690,6 +695,7 @@ async fn load_debug_sandbox_config_with_codex_home(
},
codex_home,
managed_requirements_mode,
strict_config,
)
.await
.map_err(Into::into)
@@ -700,14 +706,16 @@ async fn build_debug_sandbox_config(
harness_overrides: ConfigOverrides,
codex_home: Option<PathBuf>,
managed_requirements_mode: ManagedRequirementsMode,
strict_config: bool,
) -> std::io::Result<Config> {
let mut builder = ConfigBuilder::default()
.cli_overrides(cli_overrides)
.harness_overrides(harness_overrides);
if let ManagedRequirementsMode::Ignore = managed_requirements_mode {
.harness_overrides(harness_overrides)
.strict_config(strict_config);
if matches!(managed_requirements_mode, ManagedRequirementsMode::Ignore) {
builder = builder.loader_overrides(LoaderOverrides {
ignore_managed_requirements: true,
..Default::default()
..LoaderOverrides::default()
});
}
if let Some(codex_home) = codex_home {
@@ -776,6 +784,7 @@ mod tests {
ConfigOverrides::default(),
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
let legacy_config = build_debug_sandbox_config(
@@ -786,6 +795,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -798,6 +808,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -833,6 +844,7 @@ mod tests {
ConfigOverrides::default(),
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
let read_only_config = build_debug_sandbox_config(
@@ -843,6 +855,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -855,6 +868,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -898,6 +912,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -910,6 +925,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -935,6 +951,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -968,6 +985,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -997,6 +1015,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -1008,6 +1027,7 @@ mod tests {
ConfigOverrides::default(),
Some(codex_home.path().to_path_buf()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -1033,6 +1053,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
+7 -1
View File
@@ -10,6 +10,7 @@
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_login::CLIENT_ID;
use codex_login::CodexAuth;
use codex_login::ServerOptions;
@@ -431,7 +432,12 @@ async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config
}
};
match Config::load_with_cli_overrides(cli_overrides).await {
match ConfigBuilder::default()
.cli_overrides(cli_overrides)
.strict_config(cli_config_overrides.strict_config)
.build()
.await
{
Ok(config) => config,
Err(e) => {
eprintln!("Error loading configuration: {e}");
+62 -14
View File
@@ -52,7 +52,7 @@ use crate::marketplace_cmd::MarketplaceCli;
use crate::mcp_cmd::McpCli;
use codex_core::build_models_manager;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config::find_codex_home;
@@ -1209,11 +1209,12 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
..Default::default()
};
let config = Config::load_with_cli_overrides_and_harness_overrides(
cli_kv_overrides,
overrides,
)
.await?;
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.strict_config(root_config_overrides.strict_config)
.build()
.await?;
let mut rows = Vec::with_capacity(FEATURES.len());
let mut name_width = 0;
let mut stage_width = 0;
@@ -1372,8 +1373,12 @@ async fn run_debug_prompt_input_command(
additional_writable_roots: shared.add_dir,
..Default::default()
};
let config =
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.strict_config(root_config_overrides.strict_config)
.build()
.await?;
let mut input = shared
.images
@@ -1404,7 +1409,11 @@ async fn run_debug_models_command(
let cli_overrides = root_config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(cli_overrides).await?;
let config = ConfigBuilder::default()
.cli_overrides(cli_overrides)
.strict_config(root_config_overrides.strict_config)
.build()
.await?;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
let models_manager = build_models_manager(&config, auth_manager);
@@ -1429,8 +1438,12 @@ async fn run_debug_clear_memories_command(
config_profile: interactive.config_profile.clone(),
..Default::default()
};
let config =
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.strict_config(root_config_overrides.strict_config)
.build()
.await?;
let state_path = state_db_path(config.sqlite_home.as_path());
let mut cleared_state_db = false;
@@ -1465,9 +1478,7 @@ fn prepend_config_flags(
subcommand_config_overrides: &mut CliConfigOverrides,
cli_config_overrides: CliConfigOverrides,
) {
subcommand_config_overrides
.raw_overrides
.splice(0..0, cli_config_overrides.raw_overrides);
subcommand_config_overrides.prepend_root_overrides(cli_config_overrides);
}
fn reject_remote_mode_for_subcommand(
@@ -1672,6 +1683,7 @@ fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli)
.config_overrides
.raw_overrides
.extend(config_overrides.raw_overrides);
interactive.config_overrides.strict_config |= config_overrides.strict_config;
}
fn print_completion(cmd: CompletionCommand) {
@@ -2150,6 +2162,7 @@ mod tests {
"my-profile",
"-C",
"/tmp",
"--strict-config",
"-i",
"/tmp/a.png,/tmp/b.png",
]
@@ -2172,6 +2185,7 @@ mod tests {
Some(std::path::Path::new("/tmp"))
);
assert!(interactive.web_search);
assert!(interactive.config_overrides.strict_config);
let has_a = interactive
.images
.iter()
@@ -2605,4 +2619,38 @@ mod tests {
.expect_err("feature should be rejected");
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_config_with_unknown_enable_errors() {
let err = strict_config_feature_toggle_error(["--enable", "does_not_exist"].as_ref());
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_config_with_unknown_disable_errors() {
let err = strict_config_feature_toggle_error(["--disable", "does_not_exist"].as_ref());
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_config_with_compound_enable_errors() {
let err = strict_config_feature_toggle_error(
["--enable", "multi_agent_v2.subagent_usage_hint_text"].as_ref(),
);
assert_eq!(
err.to_string(),
"Unknown feature flag: multi_agent_v2.subagent_usage_hint_text"
);
}
fn strict_config_feature_toggle_error(args: &[&str]) -> anyhow::Error {
let cli_args = std::iter::once("codex")
.chain(std::iter::once("--strict-config"))
.chain(args.iter().copied());
let cli = MultitoolCli::try_parse_from(cli_args).expect("parse should succeed");
assert!(cli.config_overrides.strict_config);
cli.feature_toggles
.to_overrides()
.expect_err("feature should be rejected")
}
}
+10 -3
View File
@@ -2,7 +2,7 @@ use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use clap::Parser;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::find_codex_home;
use codex_core_plugins::PluginMarketplaceUpgradeOutcome;
use codex_core_plugins::PluginsManager;
@@ -70,10 +70,13 @@ impl MarketplaceCli {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let strict_config = config_overrides.strict_config;
match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
MarketplaceSubcommand::Upgrade(args) => run_upgrade(overrides, args).await?,
MarketplaceSubcommand::Upgrade(args) => {
run_upgrade(overrides, strict_config, args).await?
}
MarketplaceSubcommand::Remove(args) => run_remove(args).await?,
}
@@ -120,10 +123,14 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
async fn run_upgrade(
overrides: Vec<(String, toml::Value)>,
strict_config: bool,
args: UpgradeMarketplaceArgs,
) -> Result<()> {
let UpgradeMarketplaceArgs { marketplace_name } = args;
let config = Config::load_with_cli_overrides(overrides)
let config = ConfigBuilder::default()
.cli_overrides(overrides)
.strict_config(strict_config)
.build()
.await
.context("failed to load configuration")?;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
+18 -31
View File
@@ -11,6 +11,7 @@ use codex_config::types::McpServerConfig;
use codex_config::types::McpServerTransportConfig;
use codex_core::McpManager;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config::find_codex_home;
use codex_core::config::load_global_mcp_servers;
@@ -188,6 +189,18 @@ impl McpCli {
}
}
async fn load_config(config_overrides: &CliConfigOverrides) -> Result<Config> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
ConfigBuilder::default()
.cli_overrides(overrides)
.strict_config(config_overrides.strict_config)
.build()
.await
.context("failed to load configuration")
}
/// Preserve compatibility with servers that still expect the legacy empty-scope
/// OAuth request. If a discovered-scope request is rejected by the provider,
/// retry the login flow once without scopes.
@@ -237,13 +250,7 @@ async fn perform_oauth_login_retry_without_scopes(
}
async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Result<()> {
// Validate any provided overrides even though they are not currently applied.
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let config = load_config(config_overrides).await?;
let AddArgs {
name,
@@ -388,12 +395,7 @@ async fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveAr
}
async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) -> Result<()> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let config = load_config(config_overrides).await?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
@@ -441,12 +443,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
}
async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutArgs) -> Result<()> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let config = load_config(config_overrides).await?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
@@ -473,12 +470,7 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
}
async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> Result<()> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let config = load_config(config_overrides).await?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
@@ -728,12 +720,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
}
async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Result<()> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let config = load_config(config_overrides).await?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));