From 1e9babe1781e63bb5b3ce14c6e34435756f6c422 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Wed, 17 Dec 2025 12:08:18 -0800 Subject: [PATCH 1/2] fix: PathBuf -> AbsolutePathBuf in ConfigToml struct (#8205) We should not have any `PathBuf` fields in `ConfigToml` or any of the transitive structs we include, as we should use `AbsolutePathBuf` instead so that we do not have to keep track of the file from which `ConfigToml` was loaded such that we need it to resolve relative paths later when the values of `ConfigToml` are used. I only found two instances of this: `experimental_instructions_file` and `experimental_compact_prompt_file`. Incidentally, when these were specified as relative paths, they were resolved against `cwd` rather than `config.toml`'s parent, which seems wrong to me. I changed the behavior so they are resolved against the parent folder of the `config.toml` being parsed, which we get "for free" due to the introduction of `AbsolutePathBufGuard ` in https://github.com/openai/codex/pull/7796. While it is not great to change the behavior of a released feature, these fields are prefixed with `experimental_`, which I interpret to mean we have the liberty to change the contract. For reference: - `experimental_instructions_file` was introduced in https://github.com/openai/codex/pull/1803 - `experimental_compact_prompt_file` was introduced in https://github.com/openai/codex/pull/5959 --- codex-rs/core/src/config/mod.rs | 35 ++++++++++++++--------------- codex-rs/core/src/config/profile.rs | 6 ++--- docs/example-config.md | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index bcda301b7c..9a1ad16e94 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -26,7 +26,6 @@ use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME; use crate::protocol::AskForApproval; use crate::protocol::SandboxPolicy; -use crate::util::resolve_path; use codex_app_server_protocol::Tools; use codex_app_server_protocol::UserSavedConfig; use codex_protocol::config_types::ForcedLoginMethod; @@ -692,8 +691,8 @@ pub struct ConfigToml { pub notice: Option, /// Legacy, now use features - pub experimental_instructions_file: Option, - pub experimental_compact_prompt_file: Option, + pub experimental_instructions_file: Option, + pub experimental_compact_prompt_file: Option, pub experimental_use_unified_exec_tool: Option, pub experimental_use_rmcp_client: Option, pub experimental_use_freeform_apply_patch: Option, @@ -1132,9 +1131,8 @@ impl Config { .experimental_instructions_file .as_ref() .or(cfg.experimental_instructions_file.as_ref()); - let file_base_instructions = Self::load_override_from_file( + let file_base_instructions = Self::try_read_non_empty_file( experimental_instructions_path, - &resolved_cwd, "experimental instructions file", )?; let base_instructions = base_instructions.or(file_base_instructions); @@ -1144,9 +1142,8 @@ impl Config { .experimental_compact_prompt_file .as_ref() .or(cfg.experimental_compact_prompt_file.as_ref()); - let file_compact_prompt = Self::load_override_from_file( + let file_compact_prompt = Self::try_read_non_empty_file( experimental_compact_prompt_path, - &resolved_cwd, "experimental compact prompt file", )?; let compact_prompt = compact_prompt.or(file_compact_prompt); @@ -1278,21 +1275,21 @@ impl Config { None } - fn load_override_from_file( - path: Option<&PathBuf>, - cwd: &Path, - description: &str, + /// If `path` is `Some`, attempts to read the file at the given path and + /// returns its contents as a trimmed `String`. If the file is empty, or + /// is `Some` but cannot be read, returns an `Err`. + fn try_read_non_empty_file( + path: Option<&AbsolutePathBuf>, + context: &str, ) -> std::io::Result> { - let Some(p) = path else { + let Some(path) = path else { return Ok(None); }; - let full_path = resolve_path(cwd, p); - - let contents = std::fs::read_to_string(&full_path).map_err(|e| { + let contents = std::fs::read_to_string(path).map_err(|e| { std::io::Error::new( e.kind(), - format!("failed to read {description} {}: {e}", full_path.display()), + format!("failed to read {context} {}: {e}", path.display()), ) })?; @@ -1300,7 +1297,7 @@ impl Config { if s.is_empty() { Err(std::io::Error::new( std::io::ErrorKind::InvalidData, - format!("{description} is empty: {}", full_path.display()), + format!("{context} is empty: {}", path.display()), )) } else { Ok(Some(s)) @@ -2804,7 +2801,9 @@ model = "gpt-5.1-codex" std::fs::write(&prompt_path, " summarize differently ")?; let cfg = ConfigToml { - experimental_compact_prompt_file: Some(PathBuf::from("compact_prompt.txt")), + experimental_compact_prompt_file: Some(AbsolutePathBuf::from_absolute_path( + prompt_path, + )?), ..Default::default() }; diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index 978e1fcb63..b74b70887d 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -1,5 +1,5 @@ +use codex_utils_absolute_path::AbsolutePathBuf; use serde::Deserialize; -use std::path::PathBuf; use crate::protocol::AskForApproval; use codex_protocol::config_types::ReasoningSummary; @@ -21,8 +21,8 @@ pub struct ConfigProfile { pub model_reasoning_summary: Option, pub model_verbosity: Option, pub chatgpt_base_url: Option, - pub experimental_instructions_file: Option, - pub experimental_compact_prompt_file: Option, + pub experimental_instructions_file: Option, + pub experimental_compact_prompt_file: Option, pub include_apply_patch_tool: Option, pub experimental_use_unified_exec_tool: Option, pub experimental_use_rmcp_client: Option, diff --git a/docs/example-config.md b/docs/example-config.md index 0d09a61869..fd69faddde 100644 --- a/docs/example-config.md +++ b/docs/example-config.md @@ -311,7 +311,7 @@ experimental_use_freeform_apply_patch = false # model_reasoning_summary = "auto" # model_verbosity = "medium" # chatgpt_base_url = "https://chatgpt.com/backend-api/" -# experimental_compact_prompt_file = "compact_prompt.txt" +# experimental_compact_prompt_file = "./compact_prompt.txt" # include_apply_patch_tool = false # experimental_use_freeform_apply_patch = false # tools_web_search = false From 767fa74f8236d40050f3c5fea5592665b10462c7 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Wed, 17 Dec 2025 17:11:43 -0800 Subject: [PATCH 2/2] chore: cleanup Config instantiation codepaths --- .../app-server/src/codex_message_processor.rs | 6 +-- codex-rs/app-server/src/lib.rs | 12 +++--- codex-rs/chatgpt/src/apply_command.rs | 2 - codex-rs/cli/src/debug_sandbox.rs | 2 +- codex-rs/cli/src/login.rs | 4 +- codex-rs/cli/src/main.rs | 6 ++- codex-rs/cli/src/mcp_cmd.rs | 11 +++-- codex-rs/cloud-tasks/src/util.rs | 5 +-- codex-rs/core/src/config/mod.rs | 42 ++++++++++++------- codex-rs/exec/src/lib.rs | 3 +- codex-rs/mcp-server/src/codex_tool_config.rs | 15 +++---- codex-rs/mcp-server/src/lib.rs | 3 +- codex-rs/tui/src/lib.rs | 9 +--- codex-rs/tui2/src/lib.rs | 2 +- 14 files changed, 60 insertions(+), 62 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 412b1c7d62..44bb1294e3 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -282,7 +282,7 @@ impl CodexMessageProcessor { } async fn load_latest_config(&self) -> Result { - Config::load_with_cli_overrides(self.cli_overrides.clone(), ConfigOverrides::default()) + Config::load_with_cli_overrides(self.cli_overrides.clone()) .await .map_err(|err| JSONRPCErrorError { code: INTERNAL_ERROR_CODE, @@ -3333,7 +3333,7 @@ fn errors_to_info( async fn derive_config_from_params( overrides: ConfigOverrides, - cli_overrides: Option>, + cli_overrides: Option>, ) -> std::io::Result { let cli_overrides = cli_overrides .unwrap_or_default() @@ -3341,7 +3341,7 @@ async fn derive_config_from_params( .map(|(k, v)| (k, json_to_toml(v))) .collect(); - Config::load_with_cli_overrides(cli_overrides, overrides).await + Config::load_with_cli_overrides_and_harness_overrides(cli_overrides, overrides).await } async fn read_summary_from_rollout( diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 66d137fba4..622672dc16 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -2,7 +2,6 @@ use codex_common::CliConfigOverrides; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use std::io::ErrorKind; use std::io::Result as IoResult; use std::path::PathBuf; @@ -81,12 +80,11 @@ pub async fn run_main( format!("error parsing -c overrides: {e}"), ) })?; - let config = - Config::load_with_cli_overrides(cli_kv_overrides.clone(), ConfigOverrides::default()) - .await - .map_err(|e| { - std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}")) - })?; + let config = Config::load_with_cli_overrides(cli_kv_overrides.clone()) + .await + .map_err(|e| { + std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}")) + })?; let feedback = CodexFeedback::new(); diff --git a/codex-rs/chatgpt/src/apply_command.rs b/codex-rs/chatgpt/src/apply_command.rs index ffd460e293..e6b546281e 100644 --- a/codex-rs/chatgpt/src/apply_command.rs +++ b/codex-rs/chatgpt/src/apply_command.rs @@ -3,7 +3,6 @@ use std::path::PathBuf; use clap::Parser; use codex_common::CliConfigOverrides; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use crate::chatgpt_token::init_chatgpt_token_from_auth; use crate::get_task::GetTaskResponse; @@ -28,7 +27,6 @@ pub async fn run_apply_command( .config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?, - ConfigOverrides::default(), ) .await?; diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index c298a3511a..7aeed28fe8 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -109,7 +109,7 @@ async fn run_command_under_sandbox( log_denials: bool, ) -> anyhow::Result<()> { let sandbox_mode = create_sandbox_mode(full_auto); - let config = Config::load_with_cli_overrides( + let config = Config::load_with_cli_overrides_and_harness_overrides( config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?, diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 6681ab20c8..8fbf7b04b6 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -6,7 +6,6 @@ use codex_core::auth::CLIENT_ID; use codex_core::auth::login_with_api_key; use codex_core::auth::logout; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use codex_login::ServerOptions; use codex_login::run_device_code_login; use codex_login::run_login_server; @@ -210,8 +209,7 @@ async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config } }; - let config_overrides = ConfigOverrides::default(); - match Config::load_with_cli_overrides(cli_overrides, config_overrides).await { + match Config::load_with_cli_overrides(cli_overrides).await { Ok(config) => config, Err(e) => { eprintln!("Error loading configuration: {e}"); diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index e29e5a5e50..80db64767d 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -631,7 +631,11 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() ..Default::default() }; - let config = Config::load_with_cli_overrides(cli_kv_overrides, overrides).await?; + let config = Config::load_with_cli_overrides_and_harness_overrides( + cli_kv_overrides, + overrides, + ) + .await?; for def in codex_core::features::FEATURES.iter() { let name = def.key; let stage = stage_str(def.stage); diff --git a/codex-rs/cli/src/mcp_cmd.rs b/codex-rs/cli/src/mcp_cmd.rs index bfeedb1f78..9dcc4e2140 100644 --- a/codex-rs/cli/src/mcp_cmd.rs +++ b/codex-rs/cli/src/mcp_cmd.rs @@ -8,7 +8,6 @@ use clap::ArgGroup; use codex_common::CliConfigOverrides; use codex_common::format_env_display::format_env_display; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::find_codex_home; use codex_core::config::load_global_mcp_servers; @@ -200,7 +199,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re let overrides = config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?; - let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(overrides) .await .context("failed to load configuration")?; @@ -349,7 +348,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) let overrides = config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?; - let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(overrides) .await .context("failed to load configuration")?; @@ -392,7 +391,7 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr let overrides = config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?; - let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(overrides) .await .context("failed to load configuration")?; @@ -421,7 +420,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> let overrides = config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?; - let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(overrides) .await .context("failed to load configuration")?; @@ -678,7 +677,7 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re let overrides = config_overrides .parse_overrides() .map_err(anyhow::Error::msg)?; - let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(overrides) .await .context("failed to load configuration")?; diff --git a/codex-rs/cloud-tasks/src/util.rs b/codex-rs/cloud-tasks/src/util.rs index 79513dbcf2..9c4ae01cd6 100644 --- a/codex-rs/cloud-tasks/src/util.rs +++ b/codex-rs/cloud-tasks/src/util.rs @@ -5,7 +5,6 @@ use chrono::Utc; use reqwest::header::HeaderMap; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use codex_login::AuthManager; pub fn set_user_agent_suffix(suffix: &str) { @@ -62,9 +61,7 @@ pub fn extract_chatgpt_account_id(token: &str) -> Option { pub async fn load_auth_manager() -> Option { // TODO: pass in cli overrides once cloud tasks properly support them. - let config = Config::load_with_cli_overrides(Vec::new(), ConfigOverrides::default()) - .await - .ok()?; + let config = Config::load_with_cli_overrides(Vec::new()).await.ok()?; Some(AuthManager::new( config.codex_home, false, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 9a1ad16e94..8f2ac06b76 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -305,28 +305,40 @@ pub struct Config { } impl Config { + /// This is the preferred way to create an instance of [Config]. pub async fn load_with_cli_overrides( cli_overrides: Vec<(String, TomlValue)>, - overrides: ConfigOverrides, ) -> std::io::Result { let codex_home = find_codex_home()?; - - let root_value = load_resolved_config( - &codex_home, - cli_overrides, - crate::config_loader::LoaderOverrides::default(), + let config_toml = + load_config_as_toml_with_cli_overrides(&codex_home, cli_overrides).await?; + Self::load_from_base_config_with_overrides( + config_toml, + ConfigOverrides::default(), + codex_home, ) - .await?; + } - let cfg = deserialize_config_toml_with_base(root_value, &codex_home).map_err(|e| { - tracing::error!("Failed to deserialize overridden config: {e}"); - e - })?; - - Self::load_from_base_config_with_overrides(cfg, overrides, codex_home) + /// This is a secondary way of creating [Config], which is appropriate when + /// the harness is meant to be used with a specfic configuration that + /// ignores user settings. For example, the `codex exec` subcommand is + /// designed to use [AskForApproval::Never] exclusively. + /// + /// Further, [ConfigOverrides] contains some options that are not supported + /// in [ConfigToml], such as `cwd` and `codex_linux_sandbox_exe`. + pub async fn load_with_cli_overrides_and_harness_overrides( + cli_overrides: Vec<(String, TomlValue)>, + harness_overrides: ConfigOverrides, + ) -> std::io::Result { + let codex_home = find_codex_home()?; + let config_toml = + load_config_as_toml_with_cli_overrides(&codex_home, cli_overrides).await?; + Self::load_from_base_config_with_overrides(config_toml, harness_overrides, codex_home) } } +/// DEPRECATED: Use [Config::load_with_cli_overrides()] instead because this +/// codepath is not guaranteed to honor [ConfigRequirements]. pub async fn load_config_as_toml_with_cli_overrides( codex_home: &Path, cli_overrides: Vec<(String, TomlValue)>, @@ -927,8 +939,8 @@ pub fn resolve_oss_provider( } impl Config { - /// Meant to be used exclusively for tests: `load_with_overrides()` should - /// be used in all other cases. + /// Meant to be used exclusively for tests: + /// [Config::load_with_cli_overrides()] should be used in all other cases. pub fn load_from_base_config_with_overrides( cfg: ConfigToml, overrides: ConfigOverrides, diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 98debd5201..8559e30d57 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -202,7 +202,8 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any additional_writable_roots: add_dir, }; - let config = Config::load_with_cli_overrides(cli_kv_overrides, overrides).await?; + let config = + Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?; if let Err(err) = enforce_login_restrictions(&config).await { eprintln!("{err}"); diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index feadf0add6..16d38da45e 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -1,5 +1,7 @@ //! Configuration object accepted by the `codex` MCP tool-call. +use codex_core::config::Config; +use codex_core::config::ConfigOverrides; use codex_core::protocol::AskForApproval; use codex_protocol::config_types::SandboxMode; use codex_utils_json_to_toml::json_to_toml; @@ -139,7 +141,7 @@ impl CodexToolCallParam { pub async fn into_config( self, codex_linux_sandbox_exe: Option, - ) -> std::io::Result<(String, codex_core::config::Config)> { + ) -> std::io::Result<(String, Config)> { let Self { prompt, model, @@ -154,22 +156,17 @@ impl CodexToolCallParam { } = self; // Build the `ConfigOverrides` recognized by codex-core. - let overrides = codex_core::config::ConfigOverrides { + let overrides = ConfigOverrides { model, - review_model: None, config_profile: profile, cwd: cwd.map(PathBuf::from), approval_policy: approval_policy.map(Into::into), sandbox_mode: sandbox.map(Into::into), - model_provider: None, codex_linux_sandbox_exe, base_instructions, developer_instructions, compact_prompt, - include_apply_patch_tool: None, - show_raw_agent_reasoning: None, - tools_web_search_request: None, - additional_writable_roots: Vec::new(), + ..Default::default() }; let cli_overrides = cli_overrides @@ -179,7 +176,7 @@ impl CodexToolCallParam { .collect(); let cfg = - codex_core::config::Config::load_with_cli_overrides(cli_overrides, overrides).await?; + Config::load_with_cli_overrides_and_harness_overrides(cli_overrides, overrides).await?; Ok((prompt, cfg)) } diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index 8da5b405e2..dabd7cca0f 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; use codex_common::CliConfigOverrides; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; use mcp_types::JSONRPCMessage; use tokio::io::AsyncBufReadExt; @@ -90,7 +89,7 @@ pub async fn run_main( format!("error parsing -c overrides: {e}"), ) })?; - let config = Config::load_with_cli_overrides(cli_kv_overrides, ConfigOverrides::default()) + let config = Config::load_with_cli_overrides(cli_kv_overrides) .await .map_err(|e| { std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}")) diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 772eb19ee4..0d48c8c2ec 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -205,20 +205,15 @@ pub async fn run_main( let overrides = ConfigOverrides { model, - review_model: None, approval_policy, sandbox_mode, cwd, model_provider: model_provider_override.clone(), config_profile: cli.config_profile.clone(), codex_linux_sandbox_exe, - base_instructions: None, - developer_instructions: None, - compact_prompt: None, - include_apply_patch_tool: None, show_raw_agent_reasoning: cli.oss.then_some(true), - tools_web_search_request: None, additional_writable_roots: additional_dirs, + ..Default::default() }; let config = load_config_or_exit(cli_kv_overrides.clone(), overrides.clone()).await; @@ -552,7 +547,7 @@ async fn load_config_or_exit( overrides: ConfigOverrides, ) -> Config { #[allow(clippy::print_stderr)] - match Config::load_with_cli_overrides(cli_kv_overrides, overrides).await { + match Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await { Ok(config) => config, Err(err) => { eprintln!("Error loading configuration: {err}"); diff --git a/codex-rs/tui2/src/lib.rs b/codex-rs/tui2/src/lib.rs index 7587779234..97a4ec5e89 100644 --- a/codex-rs/tui2/src/lib.rs +++ b/codex-rs/tui2/src/lib.rs @@ -573,7 +573,7 @@ async fn load_config_or_exit( overrides: ConfigOverrides, ) -> Config { #[allow(clippy::print_stderr)] - match Config::load_with_cli_overrides(cli_kv_overrides, overrides).await { + match Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await { Ok(config) => config, Err(err) => { eprintln!("Error loading configuration: {err}");