config: add strict config parsing

This commit is contained in:
Michael Bolin
2026-05-01 09:47:57 -07:00
parent 5744b85b9a
commit bc41fe5536
25 changed files with 516 additions and 91 deletions
+26 -2
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,13 +706,18 @@ 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 {
if strict_config || matches!(managed_requirements_mode, ManagedRequirementsMode::Ignore) {
builder = builder.loader_overrides(LoaderOverrides {
ignore_managed_requirements: true,
strict_config,
ignore_managed_requirements: matches!(
managed_requirements_mode,
ManagedRequirementsMode::Ignore
),
..Default::default()
});
}
@@ -776,6 +787,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 +798,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -798,6 +811,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -833,6 +847,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 +858,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -855,6 +871,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -898,6 +915,7 @@ mod tests {
},
Some(codex_home_path.clone()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -910,6 +928,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Include,
},
Some(codex_home_path),
/*strict_config*/ false,
)
.await?;
@@ -935,6 +954,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -968,6 +988,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -997,6 +1018,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
@@ -1008,6 +1030,7 @@ mod tests {
ConfigOverrides::default(),
Some(codex_home.path().to_path_buf()),
ManagedRequirementsMode::Include,
/*strict_config*/ false,
)
.await?;
@@ -1033,6 +1056,7 @@ mod tests {
managed_requirements_mode: ManagedRequirementsMode::Ignore,
},
Some(codex_home.path().to_path_buf()),
/*strict_config*/ false,
)
.await?;
+7 -1
View File
@@ -431,7 +431,13 @@ async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config
}
};
match Config::load_with_cli_overrides(cli_overrides).await {
let loader_overrides = codex_config::LoaderOverrides {
strict_config: cli_config_overrides.strict_config,
..Default::default()
};
match Config::load_with_cli_overrides_and_loader_overrides(cli_overrides, loader_overrides)
.await
{
Ok(config) => config,
Err(e) => {
eprintln!("Error loading configuration: {e}");
+75 -12
View File
@@ -53,6 +53,7 @@ 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;
@@ -845,10 +846,11 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
None => {
let transport = listen;
let auth = auth.try_into_settings()?;
let loader_overrides = loader_overrides_from_cli(&root_config_overrides);
codex_app_server::run_main_with_transport(
arg0_paths.clone(),
root_config_overrides,
codex_config::LoaderOverrides::default(),
loader_overrides,
analytics_default_enabled,
transport,
codex_protocol::protocol::SessionSource::VSCode,
@@ -1209,11 +1211,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)
.loader_overrides(loader_overrides_from_cli(&root_config_overrides))
.build()
.await?;
let mut rows = Vec::with_capacity(FEATURES.len());
let mut name_width = 0;
let mut stage_width = 0;
@@ -1372,8 +1375,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)
.loader_overrides(loader_overrides_from_cli(&root_config_overrides))
.build()
.await?;
let mut input = shared
.images
@@ -1404,7 +1411,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 = Config::load_with_cli_overrides_and_loader_overrides(
cli_overrides,
loader_overrides_from_cli(&root_config_overrides),
)
.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 +1440,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)
.loader_overrides(loader_overrides_from_cli(root_config_overrides))
.build()
.await?;
let state_path = state_db_path(config.sqlite_home.as_path());
let mut cleared_state_db = false;
@@ -1465,11 +1480,21 @@ fn prepend_config_flags(
subcommand_config_overrides: &mut CliConfigOverrides,
cli_config_overrides: CliConfigOverrides,
) {
subcommand_config_overrides.strict_config |= cli_config_overrides.strict_config;
subcommand_config_overrides
.raw_overrides
.splice(0..0, cli_config_overrides.raw_overrides);
}
fn loader_overrides_from_cli(
config_overrides: &CliConfigOverrides,
) -> codex_config::LoaderOverrides {
codex_config::LoaderOverrides {
strict_config: config_overrides.strict_config,
..Default::default()
}
}
fn reject_remote_mode_for_subcommand(
remote: Option<&str>,
remote_auth_token_env: Option<&str>,
@@ -1569,10 +1594,11 @@ async fn run_interactive_tui(
.map(read_remote_auth_token_from_env_var)
.transpose()
.map_err(std::io::Error::other)?;
let loader_overrides = loader_overrides_from_cli(&interactive.config_overrides);
codex_tui::run_main(
interactive,
arg0_paths,
codex_config::LoaderOverrides::default(),
loader_overrides,
normalized_remote,
remote_auth_token,
)
@@ -1672,6 +1698,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 +2177,7 @@ mod tests {
"my-profile",
"-C",
"/tmp",
"--strict-check",
"-i",
"/tmp/a.png,/tmp/b.png",
]
@@ -2172,6 +2200,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 +2634,38 @@ mod tests {
.expect_err("feature should be rejected");
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_check_with_unknown_enable_errors() {
let err = strict_check_feature_toggle_error(["--enable", "does_not_exist"].as_ref());
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_check_with_unknown_disable_errors() {
let err = strict_check_feature_toggle_error(["--disable", "does_not_exist"].as_ref());
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
}
#[test]
fn strict_check_with_compound_enable_errors() {
let err = strict_check_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_check_feature_toggle_error(args: &[&str]) -> anyhow::Error {
let cli_args = std::iter::once("codex")
.chain(std::iter::once("--strict-check"))
.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")
}
}
+9 -2
View File
@@ -70,10 +70,16 @@ impl MarketplaceCli {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let loader_overrides = codex_config::LoaderOverrides {
strict_config: config_overrides.strict_config,
..Default::default()
};
match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
MarketplaceSubcommand::Upgrade(args) => run_upgrade(overrides, args).await?,
MarketplaceSubcommand::Upgrade(args) => {
run_upgrade(overrides, loader_overrides, args).await?
}
MarketplaceSubcommand::Remove(args) => run_remove(args).await?,
}
@@ -120,10 +126,11 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
async fn run_upgrade(
overrides: Vec<(String, toml::Value)>,
loader_overrides: codex_config::LoaderOverrides,
args: UpgradeMarketplaceArgs,
) -> Result<()> {
let UpgradeMarketplaceArgs { marketplace_name } = args;
let config = Config::load_with_cli_overrides(overrides)
let config = Config::load_with_cli_overrides_and_loader_overrides(overrides, loader_overrides)
.await
.context("failed to load configuration")?;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
+18 -31
View File
@@ -188,6 +188,19 @@ impl McpCli {
}
}
async fn load_config(config_overrides: &CliConfigOverrides) -> Result<Config> {
let overrides = config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let loader_overrides = codex_config::LoaderOverrides {
strict_config: config_overrides.strict_config,
..Default::default()
};
Config::load_with_cli_overrides_and_loader_overrides(overrides, loader_overrides)
.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(),
)));