Merge c4c13e2b09 into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-05-01 12:36:08 -07:00
committed by GitHub
37 changed files with 680 additions and 105 deletions
+1
View File
@@ -1482,6 +1482,7 @@
"serde_derive_1.0.228": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0.81\"}],\"features\":{\"default\":[],\"deserialize_in_place\":[]}}",
"serde_derive_internals_0.29.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}",
"serde_html_form_0.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches2\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.11\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"form_urlencoded\",\"req\":\"^1.0.1\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.45.0\"},{\"name\":\"itoa\",\"req\":\"^1.0.1\"},{\"name\":\"ryu\",\"optional\":true,\"req\":\"^1.0.9\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"}],\"features\":{\"default\":[\"ryu\",\"std\"],\"std\":[]}}",
"serde_ignored_0.1.14": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.110\"}],\"features\":{}}",
"serde_json_1.0.149": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.11\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.2.3\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2.0.2\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11.10\"},{\"default_features\":false,\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_stacker\",\"req\":\"^0.1.8\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"},{\"name\":\"zmij\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"arbitrary_precision\":[],\"default\":[\"std\"],\"float_roundtrip\":[],\"preserve_order\":[\"indexmap\",\"std\"],\"raw_value\":[],\"std\":[\"memchr/std\",\"serde_core/std\"],\"unbounded_depth\":[]}}",
"serde_path_to_error_0.1.20": "{\"dependencies\":[{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"}],\"features\":{}}",
"serde_repr_0.1.20": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.81\"}],\"features\":{}}",
+15 -4
View File
@@ -2366,6 +2366,7 @@ dependencies = [
"prost 0.14.3",
"schemars 0.8.22",
"serde",
"serde_ignored",
"serde_json",
"serde_path_to_error",
"sha2",
@@ -5305,7 +5306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -8853,7 +8854,7 @@ version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
dependencies = [
"base64 0.21.7",
"base64 0.22.1",
"chrono",
"getrandom 0.2.17",
"http 1.4.0",
@@ -9321,7 +9322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.45.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -11492,6 +11493,16 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_ignored"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde_json"
version = "1.0.149"
@@ -13965,7 +13976,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
+1
View File
@@ -338,6 +338,7 @@ seccompiler = "0.5.0"
semver = "1.0"
sentry = "0.46.0"
serde = "1"
serde_ignored = "0.1.14"
serde_json = "1"
serde_path_to_error = "0.1.20"
serde_with = "3.17"
+6
View File
@@ -337,6 +337,8 @@ pub struct InProcessClientStartArgs {
pub cli_overrides: Vec<(String, TomlValue)>,
/// Loader override knobs used by config API paths.
pub loader_overrides: LoaderOverrides,
/// Whether config API paths should reject unknown config fields.
pub strict_config: bool,
/// Preloaded cloud requirements provider.
pub cloud_requirements: CloudRequirementsLoader,
/// Feedback sink used by app-server/core telemetry and logs.
@@ -400,6 +402,7 @@ impl InProcessClientStartArgs {
config: self.config,
cli_overrides: self.cli_overrides,
loader_overrides: self.loader_overrides,
strict_config: self.strict_config,
cloud_requirements: self.cloud_requirements,
thread_config_loader,
feedback: self.feedback,
@@ -980,6 +983,7 @@ mod tests {
config: Arc::new(build_test_config().await),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -2054,6 +2058,7 @@ mod tests {
config: config.clone(),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -2093,6 +2098,7 @@ mod tests {
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -2125,6 +2125,7 @@ impl TestClientTracing {
async fn initialize(config_overrides: &[String]) -> Result<Self> {
let cli_kv_overrides = CliConfigOverrides {
raw_overrides: config_overrides.to_vec(),
strict_config: false,
}
.parse_overrides()
.map_err(|e| anyhow::anyhow!("error parsing -c overrides: {e}"))?;
@@ -10482,6 +10482,7 @@ mod tests {
temp_dir.path().to_path_buf(),
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(StaticThreadConfigLoader::new(vec![
+1
View File
@@ -824,6 +824,7 @@ mod tests {
codex_home.path().to_path_buf(),
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(codex_config::NoopThreadConfigLoader),
+9 -1
View File
@@ -30,6 +30,7 @@ pub(crate) struct ConfigManager {
cli_overrides: Arc<RwLock<Vec<(String, TomlValue)>>>,
runtime_feature_enablement: Arc<RwLock<BTreeMap<String, bool>>>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
arg0_paths: Arg0DispatchPaths,
thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,
@@ -40,6 +41,7 @@ impl ConfigManager {
codex_home: PathBuf,
cli_overrides: Vec<(String, TomlValue)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
arg0_paths: Arg0DispatchPaths,
thread_config_loader: Arc<dyn ThreadConfigLoader>,
@@ -49,6 +51,7 @@ impl ConfigManager {
cli_overrides: Arc::new(RwLock::new(cli_overrides)),
runtime_feature_enablement: Arc::new(RwLock::new(BTreeMap::new())),
loader_overrides,
strict_config,
cloud_requirements: Arc::new(RwLock::new(cloud_requirements)),
arg0_paths,
thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),
@@ -202,6 +205,7 @@ impl ConfigManager {
.codex_home(self.codex_home.clone())
.cli_overrides(merged_cli_overrides)
.loader_overrides(self.loader_overrides.clone())
.strict_config(self.strict_config)
.harness_overrides(typesafe_overrides)
.fallback_cwd(fallback_cwd)
.cloud_requirements(self.current_cloud_requirements())
@@ -230,7 +234,10 @@ impl ConfigManager {
&self.codex_home,
cwd,
&self.current_cli_overrides(),
self.loader_overrides.clone(),
codex_config::ConfigLoadOptions {
loader_overrides: self.loader_overrides.clone(),
strict_config: self.strict_config,
},
self.current_cloud_requirements(),
thread_config_loader.as_ref(),
)
@@ -265,6 +272,7 @@ impl ConfigManager {
codex_home,
cli_overrides,
loader_overrides,
/*strict_config*/ false,
cloud_requirements,
Arg0DispatchPaths::default(),
Arc::new(codex_config::NoopThreadConfigLoader),
+4
View File
@@ -118,6 +118,8 @@ pub struct InProcessStartArgs {
pub cli_overrides: Vec<(String, TomlValue)>,
/// Loader override knobs used by config API paths.
pub loader_overrides: LoaderOverrides,
/// Whether config API paths should reject unknown config fields.
pub strict_config: bool,
/// Preloaded cloud requirements provider.
pub cloud_requirements: CloudRequirementsLoader,
/// Loader used to fetch typed thread config sources before a thread starts.
@@ -403,6 +405,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
args.config.codex_home.to_path_buf(),
args.cli_overrides,
args.loader_overrides,
args.strict_config,
args.cloud_requirements,
args.arg0_paths.clone(),
args.thread_config_loader,
@@ -757,6 +760,7 @@ mod tests {
config: Arc::new(build_test_config().await),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
+2
View File
@@ -420,6 +420,7 @@ pub async fn run_main_with_transport_options(
auth: AppServerWebsocketAuthSettings,
runtime_options: AppServerRuntimeOptions,
) -> IoResult<()> {
let strict_config = cli_config_overrides.strict_config;
let environment_manager = Arc::new(
EnvironmentManager::new(EnvironmentManagerArgs::new(
ExecServerRuntimePaths::from_optional_paths(
@@ -448,6 +449,7 @@ pub async fn run_main_with_transport_options(
codex_home.to_path_buf(),
cli_kv_overrides.clone(),
loader_overrides,
strict_config,
Default::default(),
arg0_paths.clone(),
Arc::new(NoopThreadConfigLoader),
@@ -271,6 +271,7 @@ async fn build_test_processor(
config.codex_home.to_path_buf(),
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(codex_config::NoopThreadConfigLoader),
@@ -200,6 +200,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> {
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides,
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
@@ -76,6 +76,7 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides,
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
@@ -296,6 +296,7 @@ async fn thread_turns_list_reads_store_history_without_rollout_path() -> Result<
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides,
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
@@ -359,6 +360,7 @@ async fn thread_read_loaded_include_turns_reads_store_history_without_rollout_pa
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides,
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
@@ -443,6 +445,7 @@ async fn thread_list_includes_store_thread_without_rollout_path() -> Result<()>
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides,
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
+10 -8
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use clap::Parser;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_git_utils::ApplyGitRequest;
use codex_git_utils::apply_git_patch;
use codex_utils_cli::CliConfigOverrides;
@@ -23,13 +23,15 @@ pub async fn run_apply_command(
apply_cli: ApplyCommand,
cwd: Option<PathBuf>,
) -> anyhow::Result<()> {
let config = Config::load_with_cli_overrides(
apply_cli
.config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?,
)
.await?;
let cli_overrides = apply_cli
.config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = ConfigBuilder::default()
.cli_overrides(cli_overrides)
.strict_config(apply_cli.config_overrides.strict_config)
.build()
.await?;
let task_response = get_task(&config, apply_cli.task_id).await?;
apply_diff_from_task(task_response, cwd).await
+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 -11
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,6 +1478,7 @@ 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);
@@ -1672,6 +1686,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 +2165,7 @@ mod tests {
"my-profile",
"-C",
"/tmp",
"--strict-config",
"-i",
"/tmp/a.png,/tmp/b.png",
]
@@ -2172,6 +2188,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 +2622,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(),
)));
+15
View File
@@ -11,6 +11,21 @@ fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
Ok(cmd)
}
#[test]
fn strict_config_rejects_unknown_config_override() -> Result<()> {
let codex_home = TempDir::new()?;
let mut cmd = codex_command(codex_home.path())?;
cmd.args(["--strict-config", "-c", "foo=bar", "features", "list"])
.assert()
.failure()
.stderr(contains(
"unknown configuration field `foo` in -c/--config override",
));
Ok(())
}
#[tokio::test]
async fn features_enable_writes_feature_flag_to_config() -> Result<()> {
let codex_home = TempDir::new()?;
+1
View File
@@ -32,6 +32,7 @@ multimap = { workspace = true }
prost = "0.14.3"
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_ignored = { workspace = true }
serde_json = { workspace = true }
serde_path_to_error = { workspace = true }
sha2 = { workspace = true }
+117
View File
@@ -14,6 +14,7 @@ use std::fmt::Write;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use toml::Value as TomlValue;
use toml_edit::Document;
use toml_edit::Item;
use toml_edit::Table;
@@ -134,6 +135,58 @@ pub fn config_error_from_typed_toml<T: DeserializeOwned>(
}
}
pub fn config_error_from_ignored_toml_fields<T: DeserializeOwned>(
path: impl AsRef<Path>,
contents: &str,
) -> Option<ConfigError> {
let path = path.as_ref();
if let Some(error) = config_error_from_typed_toml::<T>(path, contents) {
return Some(error);
}
let deserializer = toml::de::Deserializer::parse(contents).ok()?;
let mut ignored_paths = Vec::new();
let result: Result<T, _> = serde_ignored::deserialize(deserializer, |ignored_path| {
let path_segments = ignored_path_segments(&ignored_path);
if !path_segments.is_empty() {
ignored_paths.push(path_segments);
}
});
if let Err(err) = result {
return Some(config_error_from_toml(path, contents, err));
}
let path_segments = ignored_paths.into_iter().next()?;
let ignored_path = path_segments.join(".");
let range = span_for_ignored_path(contents, &path_segments)
.map(|span| text_range_from_span(contents, span))
.unwrap_or_else(default_range);
Some(ConfigError::new(
path.to_path_buf(),
range,
format!("unknown configuration field `{ignored_path}`"),
))
}
pub fn ignored_toml_value_field<T: DeserializeOwned>(value: TomlValue) -> Option<String> {
let mut ignored_paths = Vec::new();
let result: Result<T, _> = serde_ignored::deserialize(value, |ignored_path| {
let path_segments = ignored_path_segments(&ignored_path);
if !path_segments.is_empty() {
ignored_paths.push(path_segments);
}
});
if result.is_err() {
return None;
}
ignored_paths
.into_iter()
.next()
.map(|path_segments| path_segments.join("."))
}
pub async fn first_layer_config_error<T: DeserializeOwned>(
layers: &ConfigLayerStack,
config_toml_file: &str,
@@ -323,6 +376,70 @@ fn span_for_config_path(contents: &str, path: &SerdePath) -> Option<std::ops::Ra
span_for_path(contents, path)
}
fn span_for_ignored_path(contents: &str, path: &[String]) -> Option<std::ops::Range<usize>> {
let doc = contents.parse::<Document<String>>().ok()?;
let mut node = TomlNode::Item(doc.as_item());
for (index, segment) in path.iter().enumerate() {
if index + 1 == path.len() {
let key_span = match &node {
TomlNode::Item(item) => item
.as_table_like()
.and_then(|table| table.get_key_value(segment))
.and_then(|(key, _)| key.span()),
TomlNode::Table(table) => {
table.get_key_value(segment).and_then(|(key, _)| key.span())
}
TomlNode::Value(Value::InlineTable(table)) => {
table.get_key_value(segment).and_then(|(key, _)| key.span())
}
_ => None,
};
if key_span.is_some() {
return key_span;
}
}
if let Some(next) = map_child(&node, segment) {
node = next;
continue;
}
let index = segment.parse::<usize>().ok()?;
node = seq_child(&node, index)?;
}
match node {
TomlNode::Item(item) => item.span(),
TomlNode::Table(table) => table.span(),
TomlNode::Value(value) => value.span(),
}
}
fn ignored_path_segments(path: &serde_ignored::Path<'_>) -> Vec<String> {
let mut segments = Vec::new();
push_ignored_path_segments(path, &mut segments);
segments
}
fn push_ignored_path_segments(path: &serde_ignored::Path<'_>, segments: &mut Vec<String>) {
match path {
serde_ignored::Path::Root => {}
serde_ignored::Path::Seq { parent, index } => {
push_ignored_path_segments(parent, segments);
segments.push(index.to_string());
}
serde_ignored::Path::Map { parent, key } => {
push_ignored_path_segments(parent, segments);
segments.push(key.clone());
}
serde_ignored::Path::Some { parent }
| serde_ignored::Path::NewtypeStruct { parent }
| serde_ignored::Path::NewtypeVariant { parent } => {
push_ignored_path_segments(parent, segments);
}
}
}
fn is_features_table_path(path: &SerdePath) -> bool {
let mut segments = path.iter();
matches!(segments.next(), Some(SerdeSegment::Map { key }) if key == "features")
+2
View File
@@ -64,6 +64,7 @@ pub use diagnostics::ConfigError;
pub use diagnostics::ConfigLoadError;
pub use diagnostics::TextPosition;
pub use diagnostics::TextRange;
pub use diagnostics::config_error_from_ignored_toml_fields;
pub use diagnostics::config_error_from_toml;
pub use diagnostics::config_error_from_typed_toml;
pub use diagnostics::first_layer_config_error;
@@ -114,6 +115,7 @@ pub use skills_config::SkillsConfig;
pub use state::ConfigLayerEntry;
pub use state::ConfigLayerStack;
pub use state::ConfigLayerStackOrdering;
pub use state::ConfigLoadOptions;
pub use state::LoaderOverrides;
pub use thread_config::NoopThreadConfigLoader;
pub use thread_config::RemoteThreadConfigLoader;
+2 -1
View File
@@ -10,13 +10,14 @@ This module is the canonical place to **load and describe Codex configuration la
Exported from `codex_config::loader`:
- `load_config_layers_state(fs, codex_home, cwd_opt, cli_overrides, overrides, cloud_requirements, thread_config_loader) -> ConfigLayerStack`
- `load_config_layers_state(fs, codex_home, cwd_opt, cli_overrides, options, cloud_requirements, thread_config_loader) -> ConfigLayerStack`
- `ConfigLayerStack`
- `effective_config() -> toml::Value`
- `origins() -> HashMap<String, ConfigLayerMetadata>`
- `layers_high_to_low() -> Vec<ConfigLayer>`
- `with_user_config(user_config) -> ConfigLayerStack`
- `ConfigLayerEntry` (one layer’s `{name, config, version, disabled_reason}`; `name` carries source metadata)
- `ConfigLoadOptions` (user-facing load behavior such as strict config validation)
- `LoaderOverrides` (test/override hooks for managed config sources)
- `merge_toml_values(base, overlay)` (public helper used elsewhere)
+56 -12
View File
@@ -2,11 +2,14 @@
use super::macos::ManagedAdminConfigLayer;
#[cfg(target_os = "macos")]
use super::macos::load_managed_admin_config_layer;
use crate::config_toml::ConfigToml;
use crate::diagnostics::config_error_from_ignored_toml_fields;
use crate::diagnostics::config_error_from_toml;
use crate::diagnostics::io_error_from_config_error;
use crate::state::LoaderOverrides;
use codex_file_system::ExecutorFileSystem;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use std::io;
use std::path::Path;
use std::path::PathBuf;
@@ -39,6 +42,7 @@ pub(super) async fn load_config_layers_internal(
fs: &dyn ExecutorFileSystem,
codex_home: &Path,
overrides: LoaderOverrides,
strict_config: bool,
) -> io::Result<LoadedConfigLayers> {
#[cfg(target_os = "macos")]
let LoaderOverrides {
@@ -57,19 +61,26 @@ pub(super) async fn load_config_layers_internal(
managed_config_path.unwrap_or_else(|| managed_config_default_path(codex_home)),
)?;
let managed_config =
read_config_from_path(fs, &managed_config_path, /*log_missing_as_info*/ false)
.await?
.map(|managed_config| MangedConfigFromFile {
managed_config,
file: managed_config_path.clone(),
});
let managed_config = read_config_from_path(
fs,
&managed_config_path,
/*log_missing_as_info*/ false,
strict_config,
)
.await?
.map(|loaded| MangedConfigFromFile {
managed_config: loaded,
file: managed_config_path.clone(),
});
#[cfg(target_os = "macos")]
let managed_preferences =
load_managed_admin_config_layer(managed_preferences_base64.as_deref())
.await?
.map(map_managed_admin_layer);
let managed_preferences = load_managed_admin_config_layer(
managed_preferences_base64.as_deref(),
strict_config,
codex_home,
)
.await?
.map(map_managed_admin_layer);
#[cfg(not(target_os = "macos"))]
let managed_preferences = None;
@@ -93,10 +104,14 @@ pub(super) async fn read_config_from_path(
fs: &dyn ExecutorFileSystem,
path: &AbsolutePathBuf,
log_missing_as_info: bool,
strict_config: bool,
) -> io::Result<Option<TomlValue>> {
match fs.read_file_text(path, /*sandbox*/ None).await {
Ok(contents) => match toml::from_str::<TomlValue>(&contents) {
Ok(value) => Ok(Some(value)),
Ok(value) => {
validate_config_toml_strictly_if_requested(strict_config, path, &contents)?;
Ok(Some(value))
}
Err(err) => {
tracing::error!("Failed to parse {}: {err}", path.as_path().display());
let config_error = config_error_from_toml(path.as_path(), &contents, err.clone());
@@ -122,6 +137,35 @@ pub(super) async fn read_config_from_path(
}
}
fn validate_config_toml_strictly_if_requested(
strict_config: bool,
path: &AbsolutePathBuf,
contents: &str,
) -> io::Result<()> {
if !strict_config {
return Ok(());
}
let Some(base_dir) = path.as_path().parent() else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Config file {} has no parent directory", path.display()),
));
};
let _guard = AbsolutePathBufGuard::new(base_dir);
if let Some(config_error) =
config_error_from_ignored_toml_fields::<ConfigToml>(path.as_path(), contents)
{
return Err(io_error_from_config_error(
io::ErrorKind::InvalidData,
config_error,
/*source*/ None,
));
}
Ok(())
}
/// Return the default managed config path.
pub(super) fn managed_config_default_path(codex_home: &Path) -> PathBuf {
#[cfg(unix)]
+47 -5
View File
@@ -2,13 +2,19 @@ use super::merge_requirements_with_remote_sandbox_config;
use crate::config_requirements::ConfigRequirementsToml;
use crate::config_requirements::ConfigRequirementsWithSources;
use crate::config_requirements::RequirementSource;
use crate::config_toml::ConfigToml;
use crate::diagnostics::config_error_from_ignored_toml_fields;
use crate::diagnostics::io_error_from_config_error;
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use core_foundation::string::CFStringRef;
use std::ffi::c_void;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use tokio::task;
use toml::Value as TomlValue;
@@ -31,17 +37,20 @@ pub(super) fn managed_preferences_requirements_source() -> RequirementSource {
pub(crate) async fn load_managed_admin_config_layer(
override_base64: Option<&str>,
strict_config: bool,
base_dir: &Path,
) -> io::Result<Option<ManagedAdminConfigLayer>> {
if let Some(encoded) = override_base64 {
let trimmed = encoded.trim();
return if trimmed.is_empty() {
Ok(None)
} else {
parse_managed_config_base64(trimmed).map(Some)
parse_managed_config_base64(trimmed, strict_config, base_dir).map(Some)
};
}
match task::spawn_blocking(load_managed_admin_config).await {
let base_dir = base_dir.to_path_buf();
match task::spawn_blocking(move || load_managed_admin_config(strict_config, &base_dir)).await {
Ok(result) => result,
Err(join_err) => {
if join_err.is_cancelled() {
@@ -54,11 +63,14 @@ pub(crate) async fn load_managed_admin_config_layer(
}
}
fn load_managed_admin_config() -> io::Result<Option<ManagedAdminConfigLayer>> {
fn load_managed_admin_config(
strict_config: bool,
base_dir: &Path,
) -> io::Result<Option<ManagedAdminConfigLayer>> {
load_managed_preference(MANAGED_PREFERENCES_CONFIG_KEY)?
.as_deref()
.map(str::trim)
.map(parse_managed_config_base64)
.map(|encoded| parse_managed_config_base64(encoded, strict_config, base_dir))
.transpose()
}
@@ -134,8 +146,13 @@ fn load_managed_preference(key_name: &str) -> io::Result<Option<String>> {
Ok(Some(value))
}
fn parse_managed_config_base64(encoded: &str) -> io::Result<ManagedAdminConfigLayer> {
fn parse_managed_config_base64(
encoded: &str,
strict_config: bool,
base_dir: &Path,
) -> io::Result<ManagedAdminConfigLayer> {
let raw_toml = decode_managed_preferences_base64(encoded)?;
validate_managed_config_toml_strictly_if_requested(strict_config, &raw_toml, base_dir)?;
match toml::from_str::<TomlValue>(&raw_toml) {
Ok(TomlValue::Table(parsed)) => Ok(ManagedAdminConfigLayer {
config: TomlValue::Table(parsed),
@@ -155,6 +172,31 @@ fn parse_managed_config_base64(encoded: &str) -> io::Result<ManagedAdminConfigLa
}
}
fn validate_managed_config_toml_strictly_if_requested(
strict_config: bool,
raw_toml: &str,
base_dir: &Path,
) -> io::Result<()> {
if !strict_config {
return Ok(());
}
let _guard = AbsolutePathBufGuard::new(base_dir);
let path = PathBuf::from(format!(
"{MANAGED_PREFERENCES_APPLICATION_ID}:{MANAGED_PREFERENCES_CONFIG_KEY}"
));
if let Some(config_error) = config_error_from_ignored_toml_fields::<ConfigToml>(&path, raw_toml)
{
return Err(io_error_from_config_error(
io::ErrorKind::InvalidData,
config_error,
/*source*/ None,
));
}
Ok(())
}
fn parse_managed_requirements_base64(encoded: &str) -> io::Result<ConfigRequirementsToml> {
toml::from_str::<ConfigRequirementsToml>(&decode_managed_preferences_base64(encoded)?).map_err(
|err| {
+85 -12
View File
@@ -12,8 +12,10 @@ use crate::config_requirements::SandboxModeRequirement;
use crate::config_toml::ConfigToml;
use crate::config_toml::ProjectConfig;
use crate::diagnostics::ConfigError;
use crate::diagnostics::config_error_from_ignored_toml_fields;
use crate::diagnostics::config_error_from_toml;
use crate::diagnostics::first_layer_config_error_from_entries as typed_first_layer_config_error_from_entries;
use crate::diagnostics::ignored_toml_value_field;
use crate::diagnostics::io_error_from_config_error;
use crate::merge::merge_toml_values;
use crate::overrides::build_cli_overrides_layer;
@@ -21,6 +23,7 @@ use crate::project_root_markers::default_project_root_markers;
use crate::project_root_markers::project_root_markers_from_config;
use crate::state::ConfigLayerEntry;
use crate::state::ConfigLayerStack;
use crate::state::ConfigLoadOptions;
use crate::state::LoaderOverrides;
use crate::thread_config::ThreadConfigContext;
use crate::thread_config::ThreadConfigLoader;
@@ -103,10 +106,14 @@ pub async fn load_config_layers_state(
codex_home: &Path,
cwd: Option<AbsolutePathBuf>,
cli_overrides: &[(String, TomlValue)],
overrides: LoaderOverrides,
options: impl Into<ConfigLoadOptions>,
cloud_requirements: CloudRequirementsLoader,
thread_config_loader: &dyn ThreadConfigLoader,
) -> io::Result<ConfigLayerStack> {
let ConfigLoadOptions {
loader_overrides: overrides,
strict_config,
} = options.into();
let ignore_managed_requirements = overrides.ignore_managed_requirements;
let ignore_user_config = overrides.ignore_user_config;
let ignore_user_and_project_exec_policy_rules =
@@ -139,7 +146,8 @@ pub async fn load_config_layers_state(
// Make a best-effort to support the legacy `managed_config.toml` as a
// requirements specification.
let loaded_config_layers =
layer_io::load_config_layers_internal(fs, codex_home, overrides.clone()).await?;
layer_io::load_config_layers_internal(fs, codex_home, overrides.clone(), strict_config)
.await?;
if !ignore_managed_requirements {
load_requirements_from_legacy_scheme(
&mut config_requirements_toml,
@@ -163,6 +171,7 @@ pub async fn load_config_layers_state(
None
} else {
let cli_overrides_layer = build_cli_overrides_layer(cli_overrides);
validate_cli_overrides_strictly_if_requested(strict_config, &cli_overrides_layer)?;
let base_dir = cwd
.as_ref()
.map(AbsolutePathBuf::as_path)
@@ -176,16 +185,20 @@ pub async fn load_config_layers_state(
// Include an entry for the "system" config folder, loading its config.toml,
// if it exists.
let system_config_toml_file = system_config_toml_file_with_overrides(&overrides)?;
let system_layer =
load_config_toml_for_required_layer(fs, &system_config_toml_file, |config_toml| {
let system_layer = load_config_toml_for_required_layer(
fs,
&system_config_toml_file,
strict_config,
|config_toml| {
ConfigLayerEntry::new(
ConfigLayerSource::System {
file: system_config_toml_file.clone(),
},
config_toml,
)
})
.await?;
},
)
.await?;
layers.push(system_layer);
// Add a layer for $CODEX_HOME/config.toml so folder-derived resources such
@@ -200,7 +213,7 @@ pub async fn load_config_layers_state(
TomlValue::Table(toml::map::Map::new()),
)
} else {
load_config_toml_for_required_layer(fs, &user_file, |config_toml| {
load_config_toml_for_required_layer(fs, &user_file, strict_config, |config_toml| {
ConfigLayerEntry::new(
ConfigLayerSource::User {
file: user_file.clone(),
@@ -267,6 +280,7 @@ pub async fn load_config_layers_state(
&project_trust_context.project_root,
&project_trust_context,
codex_home,
strict_config,
)
.await?;
layers.extend(project_layers.layers);
@@ -358,15 +372,11 @@ fn insert_layer_by_precedence(layers: &mut Vec<ConfigLayerEntry>, layer: ConfigL
async fn load_config_toml_for_required_layer(
fs: &dyn ExecutorFileSystem,
toml_file: &AbsolutePathBuf,
strict_config: bool,
create_entry: impl FnOnce(TomlValue) -> ConfigLayerEntry,
) -> io::Result<ConfigLayerEntry> {
let toml_value = match fs.read_file_text(toml_file, /*sandbox*/ None).await {
Ok(contents) => {
let config: TomlValue = toml::from_str(&contents).map_err(|err| {
let config_error =
config_error_from_toml(toml_file.as_path(), &contents, err.clone());
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
})?;
let config_parent = toml_file.as_path().parent().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
@@ -376,6 +386,17 @@ async fn load_config_toml_for_required_layer(
),
)
})?;
let config: TomlValue = toml::from_str(&contents).map_err(|err| {
let config_error =
config_error_from_toml(toml_file.as_path(), &contents, err.clone());
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
})?;
validate_config_toml_strictly_if_requested(
strict_config,
toml_file.as_path(),
&contents,
config_parent,
)?;
resolve_relative_paths_in_config_toml(config, config_parent)
}
Err(e) => {
@@ -396,6 +417,49 @@ async fn load_config_toml_for_required_layer(
Ok(create_entry(toml_value))
}
fn validate_config_toml_strictly_if_requested(
strict_config: bool,
toml_file: &Path,
contents: &str,
base_dir: &Path,
) -> io::Result<()> {
if !strict_config {
return Ok(());
}
let _guard = AbsolutePathBufGuard::new(base_dir);
if let Some(config_error) =
config_error_from_ignored_toml_fields::<ConfigToml>(toml_file, contents)
{
return Err(io_error_from_config_error(
io::ErrorKind::InvalidData,
config_error,
/*source*/ None,
));
}
Ok(())
}
fn validate_cli_overrides_strictly_if_requested(
strict_config: bool,
cli_overrides_layer: &TomlValue,
) -> io::Result<()> {
if !strict_config {
return Ok(());
}
if let Some(ignored_path) = ignored_toml_value_field::<ConfigToml>(cli_overrides_layer.clone())
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unknown configuration field `{ignored_path}` in -c/--config override"),
));
}
Ok(())
}
/// If available, apply requirements from the platform system
/// `requirements.toml` location to `config_requirements_toml` by filling in
/// any unset fields.
@@ -960,6 +1024,7 @@ async fn load_project_layers(
project_root: &AbsolutePathBuf,
trust_context: &ProjectTrustContext,
codex_home: &Path,
strict_config: bool,
) -> io::Result<LoadedProjectLayers> {
let codex_home_abs = AbsolutePathBuf::from_absolute_path(codex_home)?;
let codex_home_normalized =
@@ -1023,6 +1088,14 @@ async fn load_project_layers(
}
};
let mut config = config;
if disabled_reason.is_none() {
validate_config_toml_strictly_if_requested(
strict_config,
config_file.as_path(),
&contents,
dot_codex_abs.as_path(),
)?;
}
let ignored_project_config_keys = sanitize_project_config(&mut config);
let config =
resolve_relative_paths_in_config_toml(config, dot_codex_abs.as_path())?;
+16
View File
@@ -14,6 +14,22 @@ use std::collections::HashMap;
use std::path::PathBuf;
use toml::Value as TomlValue;
/// User-facing config loading behavior that is not part of the config document.
#[derive(Debug, Default, Clone)]
pub struct ConfigLoadOptions {
pub loader_overrides: LoaderOverrides,
pub strict_config: bool,
}
impl From<LoaderOverrides> for ConfigLoadOptions {
fn from(loader_overrides: LoaderOverrides) -> Self {
Self {
loader_overrides,
strict_config: false,
}
}
}
/// LoaderOverrides overrides managed configuration inputs (primarily for tests).
#[derive(Debug, Default, Clone)]
pub struct LoaderOverrides {
@@ -18,6 +18,7 @@ use codex_config::RequirementSource;
use codex_config::SessionThreadConfig;
use codex_config::StaticThreadConfigLoader;
use codex_config::ThreadConfigSource;
use codex_config::config_error_from_ignored_toml_fields;
use codex_config::config_error_from_toml;
use codex_config::config_toml::ConfigToml;
use codex_config::config_toml::ProjectConfig;
@@ -228,6 +229,70 @@ async fn returns_config_error_for_schema_error_in_user_config() {
assert_eq!(config_error, &expected_config_error);
}
#[tokio::test]
async fn strict_config_rejects_unknown_user_config_key() {
let tmp = tempdir().expect("tempdir");
let contents = "model = \"gpt-5\"\nunknown_key = true";
let config_path = tmp.path().join(CONFIG_TOML_FILE);
std::fs::write(&config_path, contents).expect("write config");
let err = ConfigBuilder::default()
.codex_home(tmp.path().to_path_buf())
.fallback_cwd(Some(tmp.path().to_path_buf()))
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
.strict_config(/*strict_config*/ true)
.build()
.await
.expect_err("expected error");
let config_error = config_error_from_io(&err);
let expected_config_error =
config_error_from_ignored_toml_fields::<ConfigToml>(&config_path, contents)
.expect("unknown field error");
assert_eq!(config_error, &expected_config_error);
}
#[tokio::test]
async fn strict_config_rejects_unknown_cli_override_key() {
let tmp = tempdir().expect("tempdir");
let err = ConfigBuilder::default()
.codex_home(tmp.path().to_path_buf())
.fallback_cwd(Some(tmp.path().to_path_buf()))
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
.cli_overrides(vec![(
"foo".to_string(),
TomlValue::String("bar".to_string()),
)])
.strict_config(/*strict_config*/ true)
.build()
.await
.expect_err("expected error");
assert_eq!(
err.to_string(),
"unknown configuration field `foo` in -c/--config override"
);
}
#[test]
fn strict_config_points_to_unknown_nested_key() {
let tmp = tempdir().expect("tempdir");
let contents = "[mcp_servers.local]\ncommand = \"echo\"\nunknown_key = true";
let config_path = tmp.path().join(CONFIG_TOML_FILE);
std::fs::write(&config_path, contents).expect("write config");
let error = config_error_from_ignored_toml_fields::<ConfigToml>(&config_path, contents)
.expect("unknown field error");
assert_eq!(
error.message,
"unknown configuration field `mcp_servers.local.unknown_key`"
);
assert_eq!(error.range.start.line, 3);
assert_eq!(error.range.start.column, 1);
}
#[test]
fn schema_error_points_to_feature_value() {
let tmp = tempdir().expect("tempdir");
+35 -3
View File
@@ -15,7 +15,6 @@ use codex_config::ConfigRequirements;
use codex_config::ConfigRequirementsToml;
use codex_config::ConstrainedWithSource;
use codex_config::FeatureRequirementsToml;
use codex_config::LoaderOverrides;
use codex_config::McpServerIdentity;
use codex_config::McpServerRequirement;
use codex_config::PluginRequirementsToml;
@@ -132,9 +131,11 @@ mod network_proxy_spec;
mod permissions;
#[cfg(test)]
mod schema;
pub use codex_config::ConfigLoadOptions;
pub use codex_config::Constrained;
pub use codex_config::ConstraintError;
pub use codex_config::ConstraintResult;
pub use codex_config::LoaderOverrides;
pub use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub use codex_sandboxing::system_bwrap_warning;
@@ -878,6 +879,7 @@ pub struct ConfigBuilder {
cli_overrides: Option<Vec<(String, TomlValue)>>,
harness_overrides: Option<ConfigOverrides>,
loader_overrides: Option<LoaderOverrides>,
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
thread_config_loader: Option<Arc<dyn ThreadConfigLoader>>,
fallback_cwd: Option<PathBuf>,
@@ -904,6 +906,11 @@ impl ConfigBuilder {
self
}
pub fn strict_config(mut self, strict_config: bool) -> Self {
self.strict_config = strict_config;
self
}
pub fn cloud_requirements(mut self, cloud_requirements: CloudRequirementsLoader) -> Self {
self.cloud_requirements = cloud_requirements;
self
@@ -933,6 +940,7 @@ impl ConfigBuilder {
cli_overrides,
harness_overrides,
loader_overrides,
strict_config,
cloud_requirements,
thread_config_loader,
fallback_cwd,
@@ -955,7 +963,10 @@ impl ConfigBuilder {
&codex_home,
Some(cwd),
&cli_overrides,
loader_overrides,
ConfigLoadOptions {
loader_overrides,
strict_config,
},
cloud_requirements,
thread_config_loader
.as_deref()
@@ -1132,6 +1143,17 @@ impl Config {
.await
}
pub async fn load_with_cli_overrides_and_loader_overrides(
cli_overrides: Vec<(String, TomlValue)>,
loader_overrides: LoaderOverrides,
) -> std::io::Result<Self> {
ConfigBuilder::default()
.cli_overrides(cli_overrides)
.loader_overrides(loader_overrides)
.build()
.await
}
/// Load a default configuration when user config files are invalid.
pub async fn load_default_with_cli_overrides(
cli_overrides: Vec<(String, TomlValue)>,
@@ -1212,13 +1234,23 @@ pub async fn load_config_as_toml_with_cli_and_loader_overrides(
cwd: Option<&AbsolutePathBuf>,
cli_overrides: Vec<(String, TomlValue)>,
loader_overrides: LoaderOverrides,
) -> std::io::Result<ConfigToml> {
load_config_as_toml_with_cli_and_load_options(codex_home, cwd, cli_overrides, loader_overrides)
.await
}
pub async fn load_config_as_toml_with_cli_and_load_options(
codex_home: &Path,
cwd: Option<&AbsolutePathBuf>,
cli_overrides: Vec<(String, TomlValue)>,
options: impl Into<ConfigLoadOptions>,
) -> std::io::Result<ConfigToml> {
let config_layer_stack = load_config_layers_state(
LOCAL_FS.as_ref(),
codex_home,
cwd.cloned(),
&cli_overrides,
loader_overrides,
options,
CloudRequirementsLoader::default(),
&codex_config::NoopThreadConfigLoader,
)
+10 -3
View File
@@ -55,6 +55,7 @@ use codex_app_server_protocol::TurnStartedNotification;
use codex_arg0::Arg0DispatchPaths;
use codex_cloud_requirements::cloud_requirements_loader_for_storage;
use codex_config::ConfigLoadError;
use codex_config::ConfigLoadOptions;
use codex_config::LoaderOverrides;
use codex_config::format_config_error_with_source;
use codex_core::check_execpolicy_for_warnings;
@@ -62,7 +63,7 @@ use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
use codex_core::config::find_codex_home;
use codex_core::config::load_config_as_toml_with_cli_and_loader_overrides;
use codex_core::config::load_config_as_toml_with_cli_and_load_options;
use codex_core::config::resolve_oss_provider;
use codex_core::find_thread_meta_by_name_str;
use codex_core::format_exec_policy_error_with_source;
@@ -314,17 +315,21 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
};
#[allow(clippy::print_stderr)]
let strict_config = config_overrides.strict_config;
let loader_overrides = LoaderOverrides {
ignore_user_config,
ignore_user_and_project_exec_policy_rules: ignore_rules,
..Default::default()
};
let config_toml = match load_config_as_toml_with_cli_and_loader_overrides(
let config_toml = match load_config_as_toml_with_cli_and_load_options(
&codex_home,
Some(&config_cwd),
cli_kv_overrides.clone(),
loader_overrides.clone(),
ConfigLoadOptions {
loader_overrides: loader_overrides.clone(),
strict_config,
},
)
.await
{
@@ -425,6 +430,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.loader_overrides(loader_overrides)
.strict_config(strict_config)
.cloud_requirements(cloud_requirements)
.build()
.await?;
@@ -508,6 +514,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
config: std::sync::Arc::new(config.clone()),
cli_overrides: run_cli_overrides,
loader_overrides: run_loader_overrides,
strict_config,
cloud_requirements: run_cloud_requirements,
feedback: CodexFeedback::new(),
log_db: None,
+5 -2
View File
@@ -6,7 +6,7 @@ use std::io::Result as IoResult;
use std::sync::Arc;
use codex_arg0::Arg0DispatchPaths;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentManagerArgs;
use codex_exec_server::ExecServerRuntimePaths;
@@ -77,7 +77,10 @@ pub async fn run_main(
format!("error parsing -c overrides: {e}"),
)
})?;
let config = Config::load_with_cli_overrides(cli_kv_overrides)
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.strict_config(cli_config_overrides.strict_config)
.build()
.await
.map_err(|e| {
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
+38 -3
View File
@@ -8,7 +8,7 @@ use crate::legacy_core::config::Config;
use crate::legacy_core::config::ConfigBuilder;
use crate::legacy_core::config::ConfigOverrides;
use crate::legacy_core::config::find_codex_home;
use crate::legacy_core::config::load_config_as_toml_with_cli_overrides;
use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options;
use crate::legacy_core::config::resolve_oss_provider;
use crate::legacy_core::format_exec_policy_error_with_source;
use crate::legacy_core::windows_sandbox::WindowsSandboxLevelExt;
@@ -267,6 +267,7 @@ async fn start_embedded_app_server(
config: Config,
cli_kv_overrides: Vec<(String, toml::Value)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
feedback: codex_feedback::CodexFeedback,
log_db: Option<log_db::LogDbLayer>,
@@ -277,6 +278,7 @@ async fn start_embedded_app_server(
config,
cli_kv_overrides,
loader_overrides,
strict_config,
cloud_requirements,
feedback,
log_db,
@@ -394,6 +396,7 @@ async fn start_app_server(
config: Config,
cli_kv_overrides: Vec<(String, toml::Value)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
feedback: codex_feedback::CodexFeedback,
log_db: Option<log_db::LogDbLayer>,
@@ -405,6 +408,7 @@ async fn start_app_server(
config,
cli_kv_overrides,
loader_overrides,
strict_config,
cloud_requirements,
feedback,
log_db,
@@ -430,6 +434,7 @@ pub(crate) async fn start_app_server_for_picker(
config.clone(),
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
codex_feedback::CodexFeedback::new(),
/*log_db*/ None,
@@ -457,6 +462,7 @@ async fn start_embedded_app_server_with<F, Fut>(
config: Config,
cli_kv_overrides: Vec<(String, toml::Value)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
feedback: codex_feedback::CodexFeedback,
log_db: Option<log_db::LogDbLayer>,
@@ -482,6 +488,7 @@ where
config: Arc::new(config),
cli_overrides: cli_kv_overrides,
loader_overrides,
strict_config,
cloud_requirements,
feedback,
log_db,
@@ -688,6 +695,7 @@ pub async fn run_main(
remote: Option<String>,
remote_auth_token: Option<String>,
) -> std::io::Result<AppExitInfo> {
let strict_config = cli.config_overrides.strict_config;
let remote_url = remote;
if let (Some(websocket_url), Some(_)) = (remote_url.as_deref(), remote_auth_token.as_ref()) {
validate_remote_auth_token_transport(websocket_url).map_err(std::io::Error::other)?;
@@ -726,7 +734,10 @@ pub async fn run_main(
// gpt-oss:20b) and ensure it is present locally. Also, force the built‑in
let raw_overrides = cli.config_overrides.raw_overrides.clone();
// `oss` model provider.
let overrides_cli = codex_utils_cli::CliConfigOverrides { raw_overrides };
let overrides_cli = codex_utils_cli::CliConfigOverrides {
raw_overrides,
strict_config: false,
};
let cli_kv_overrides = match overrides_cli.parse_overrides() {
// Parse `-c` overrides from the CLI.
Ok(v) => v,
@@ -761,10 +772,14 @@ pub async fn run_main(
config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target, &environment_manager)?;
#[allow(clippy::print_stderr)]
let config_toml = match load_config_as_toml_with_cli_overrides(
let config_toml = match load_config_as_toml_with_cli_and_load_options(
&codex_home,
config_cwd.as_ref(),
cli_kv_overrides.clone(),
codex_config::ConfigLoadOptions {
loader_overrides: loader_overrides.clone(),
strict_config,
},
)
.await
{
@@ -868,6 +883,8 @@ pub async fn run_main(
cli_kv_overrides.clone(),
overrides.clone(),
cloud_requirements.clone(),
loader_overrides.clone(),
strict_config,
)
.await;
@@ -1020,6 +1037,7 @@ pub async fn run_main(
cli,
arg0_paths,
loader_overrides,
strict_config,
app_server_target,
remote_cwd_override,
config,
@@ -1041,6 +1059,7 @@ async fn run_ratatui_app(
cli: Cli,
arg0_paths: Arg0DispatchPaths,
loader_overrides: LoaderOverrides,
strict_config: bool,
app_server_target: AppServerTarget,
remote_cwd_override: Option<PathBuf>,
initial_config: Config,
@@ -1105,6 +1124,7 @@ async fn run_ratatui_app(
initial_config.clone(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
strict_config,
cloud_requirements.clone(),
feedback.clone(),
log_db.clone(),
@@ -1191,6 +1211,8 @@ async fn run_ratatui_app(
cli_kv_overrides.clone(),
overrides.clone(),
cloud_requirements.clone(),
loader_overrides.clone(),
strict_config,
)
.await
} else {
@@ -1392,6 +1414,8 @@ async fn run_ratatui_app(
cli_kv_overrides.clone(),
overrides.clone(),
cloud_requirements.clone(),
loader_overrides.clone(),
strict_config,
fallback_cwd,
)
.await
@@ -1434,6 +1458,7 @@ async fn run_ratatui_app(
config.clone(),
cli_kv_overrides.clone(),
loader_overrides,
strict_config,
cloud_requirements.clone(),
feedback.clone(),
log_db.clone(),
@@ -1586,11 +1611,15 @@ async fn load_config_or_exit(
cli_kv_overrides: Vec<(String, toml::Value)>,
overrides: ConfigOverrides,
cloud_requirements: CloudRequirementsLoader,
loader_overrides: LoaderOverrides,
strict_config: bool,
) -> Config {
load_config_or_exit_with_fallback_cwd(
cli_kv_overrides,
overrides,
cloud_requirements,
loader_overrides,
strict_config,
/*fallback_cwd*/ None,
)
.await
@@ -1600,12 +1629,16 @@ async fn load_config_or_exit_with_fallback_cwd(
cli_kv_overrides: Vec<(String, toml::Value)>,
overrides: ConfigOverrides,
cloud_requirements: CloudRequirementsLoader,
loader_overrides: LoaderOverrides,
strict_config: bool,
fallback_cwd: Option<PathBuf>,
) -> Config {
#[allow(clippy::print_stderr)]
match ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.loader_overrides(loader_overrides)
.strict_config(strict_config)
.cloud_requirements(cloud_requirements)
.fallback_cwd(fallback_cwd)
.build()
@@ -1676,6 +1709,7 @@ mod tests {
config,
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
codex_feedback::CodexFeedback::new(),
/*log_db*/ None,
@@ -2022,6 +2056,7 @@ mod tests {
config,
Vec::new(),
LoaderOverrides::default(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
codex_feedback::CodexFeedback::new(),
/*log_db*/ None,
+2 -2
View File
@@ -2,7 +2,6 @@ use clap::Parser;
use codex_app_server_client::legacy_core;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
use codex_config::LoaderOverrides;
use codex_tui::AppExitInfo;
use codex_tui::Cli;
use codex_tui::ExitReason;
@@ -49,6 +48,7 @@ fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
let top_cli = TopCli::parse();
let mut inner = top_cli.inner;
inner.config_overrides.strict_config |= top_cli.config_overrides.strict_config;
inner
.config_overrides
.raw_overrides
@@ -56,7 +56,7 @@ fn main() -> anyhow::Result<()> {
let exit_info = run_main(
inner,
arg0_paths,
LoaderOverrides::default(),
Default::default(),
/*remote*/ None,
/*remote_auth_token*/ None,
)
+1
View File
@@ -1039,6 +1039,7 @@ mod tests {
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides: Default::default(),
strict_config: false,
cloud_requirements: cloud_requirements_loader_for_storage(
codex_home_path.clone(),
/*enable_codex_api_key_env*/ false,
@@ -17,6 +17,10 @@ use toml::Value;
/// calling code can decide how to interpret the right-hand side.
#[derive(Parser, Debug, Default, Clone)]
pub struct CliConfigOverrides {
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
#[arg(long = "strict-config", global = true, default_value_t = false)]
pub strict_config: bool,
/// Override a configuration value that would otherwise be loaded from
/// `~/.codex/config.toml`. Use a dotted path (`foo.bar.baz`) to override
/// nested values. The `value` portion is parsed as TOML. If it fails to
@@ -184,6 +188,7 @@ mod tests {
fn canonicalizes_use_legacy_landlock_alias() {
let overrides = CliConfigOverrides {
raw_overrides: vec!["use_legacy_landlock=true".to_string()],
strict_config: false,
};
let parsed = overrides.parse_overrides().expect("parse_overrides");
assert_eq!(parsed[0].0.as_str(), "features.use_legacy_landlock");