Refine cloud config bundle runtime integration

Move CloudConfigBundleLoader into ConfigLoadOptions so lower-level config loading does not need a separate bundle parameter or a bundle-specific TOML helper.

Keep exec and TUI on the straightforward local helper path: bootstrap from local config, build the shared cloud bundle loader, and reload merged TOML with that loader only when OSS provider inference needs cloud-delivered config.

Preserve strict validation and path-base behavior for cloud config and requirements fragments, including diagnostics that name the cloud layer and requirements paths resolved against CODEX_HOME.

Validation: just fmt; just test -p codex-core -E 'test(load_config_layers_resolves_relative_cloud_requirements_paths_against_codex_home) | test(strict_config_rejects_unknown_cloud_config_key)'; just test -p codex-config; just test -p codex-exec -E 'test(top_cli_parses_resume_prompt_after_config_flag)'; just test -p codex-tui -E 'test(app_server_target_for_launch_prefers_explicit_remote_endpoint)'; just fix -p codex-config -p codex-core -p codex-exec -p codex-tui -p codex-app-server -p codex-core-plugins; git diff --check.
This commit is contained in:
Joe Florencio
2026-05-26 14:37:35 -07:00
parent 8c6213aaa3
commit 86ae66fda7
8 changed files with 149 additions and 75 deletions

View File

@@ -274,8 +274,8 @@ impl ConfigManager {
codex_config::ConfigLoadOptions {
loader_overrides: self.loader_overrides.clone(),
strict_config: self.strict_config,
cloud_config_bundle: self.current_cloud_config_bundle(),
},
self.current_cloud_config_bundle(),
thread_config_loader.as_ref(),
)
.await

View File

@@ -10,7 +10,7 @@ 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, options, cloud_config_bundle, thread_config_loader) -> ConfigLayerStack`
- `load_config_layers_state(fs, codex_home, cwd_opt, cli_overrides, options, thread_config_loader) -> ConfigLayerStack`
- `ConfigLayerStack`
- `effective_config() -> toml::Value`
- `origins() -> HashMap<String, ConfigLayerMetadata>`
@@ -48,7 +48,6 @@ computing the effective config and origins metadata. This is what
Most callers want the effective config plus metadata:
```rust
use codex_config::CloudConfigBundleLoader;
use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::loader::load_config_layers_state;
@@ -64,7 +63,6 @@ let layers = load_config_layers_state(
Some(cwd),
&cli_overrides,
LoaderOverrides::default(),
CloudConfigBundleLoader::default(),
&NoopThreadConfigLoader,
).await?;

View File

@@ -107,7 +107,6 @@ model = "gpt-work"
/*cwd*/ None,
&[],
overrides,
CloudConfigBundleLoader::default(),
&crate::NoopThreadConfigLoader,
)
.await
@@ -166,7 +165,6 @@ model = "gpt-main"
/*cwd*/ None,
&[],
overrides,
CloudConfigBundleLoader::default(),
&crate::NoopThreadConfigLoader,
)
.await
@@ -223,7 +221,6 @@ model = "gpt-dev"
/*cwd*/ None,
&[],
overrides,
CloudConfigBundleLoader::default(),
&crate::NoopThreadConfigLoader,
)
.await

View File

@@ -3,7 +3,6 @@ use std::path::Path;
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
use crate::PluginsConfigInput;
use codex_config::CloudConfigBundleLoader;
use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::loader::load_config_layers_state;
@@ -106,7 +105,6 @@ pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> Plugin
Some(cwd),
&[],
LoaderOverrides::without_managed_config_for_tests(),
CloudConfigBundleLoader::default(),
&NoopThreadConfigLoader,
)
.await

View File

@@ -1152,8 +1152,8 @@ impl ConfigBuilder {
ConfigLoadOptions {
loader_overrides,
strict_config,
cloud_config_bundle,
},
cloud_config_bundle,
thread_config_loader
.as_deref()
.unwrap_or(&codex_config::NoopThreadConfigLoader),
@@ -1543,7 +1543,6 @@ pub async fn load_config_as_toml_with_cli_and_load_options(
cwd.cloned(),
&cli_overrides,
options,
CloudConfigBundleLoader::default(),
&codex_config::NoopThreadConfigLoader,
)
.await?;
@@ -1754,7 +1753,6 @@ pub async fn load_global_mcp_servers(
cwd,
&cli_overrides,
LoaderOverrides::default(),
CloudConfigBundleLoader::default(),
&codex_config::NoopThreadConfigLoader,
)
.await?;

View File

@@ -10,7 +10,6 @@ use anyhow::Result;
use async_trait::async_trait;
use codex_app_server_protocol::ConfigLayerSource;
use codex_config::CONFIG_TOML_FILE;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::LoaderOverrides;
@@ -60,7 +59,6 @@ async fn build_config_state_with_mtimes() -> Result<(ConfigState, Vec<LayerMtime
/*cwd*/ None,
&cli_overrides,
overrides,
CloudConfigBundleLoader::default(),
&codex_config::NoopThreadConfigLoader,
)
.await

View File

@@ -52,6 +52,7 @@ use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStartedNotification;
use codex_arg0::Arg0DispatchPaths;
use codex_cloud_config::cloud_config_bundle_loader_for_storage;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLoadError;
use codex_config::ConfigLoadOptions;
use codex_config::LoaderOverrides;
@@ -328,43 +329,26 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
..Default::default()
};
let config_toml = match load_config_as_toml_with_cli_and_load_options(
let bootstrap_config_toml = load_config_toml_or_exit(
&codex_home,
Some(&config_cwd),
cli_kv_overrides.clone(),
ConfigLoadOptions {
loader_overrides: loader_overrides.clone(),
strict_config,
},
loader_overrides.clone(),
strict_config,
CloudConfigBundleLoader::default(),
)
.await
{
Ok(config_toml) => config_toml,
Err(err) => {
let config_error = err
.get_ref()
.and_then(|err| err.downcast_ref::<ConfigLoadError>())
.map(ConfigLoadError::config_error);
if let Some(config_error) = config_error {
eprintln!(
"Error loading config.toml:\n{}",
format_config_error_with_source(config_error)
);
} else {
eprintln!("Error loading config.toml: {err}");
}
std::process::exit(1);
}
};
.await;
let chatgpt_base_url = config_toml
let chatgpt_base_url = bootstrap_config_toml
.chatgpt_base_url
.clone()
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string());
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
codex_home.to_path_buf(),
/*enable_codex_api_key_env*/ false,
config_toml.cli_auth_credentials_store.unwrap_or_default(),
bootstrap_config_toml
.cli_auth_credentials_store
.unwrap_or_default(),
chatgpt_base_url,
)
.await;
@@ -372,8 +356,24 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
let run_loader_overrides = loader_overrides.clone();
let run_cloud_config_bundle = cloud_config_bundle.clone();
let config_toml_for_oss;
let config_toml_for_oss = if oss && oss_provider.is_none() {
config_toml_for_oss = load_config_toml_or_exit(
&codex_home,
Some(&config_cwd),
cli_kv_overrides.clone(),
loader_overrides.clone(),
strict_config,
cloud_config_bundle.clone(),
)
.await;
&config_toml_for_oss
} else {
&bootstrap_config_toml
};
let model_provider = if oss {
let resolved = resolve_oss_provider(oss_provider.as_deref(), &config_toml);
let resolved = resolve_oss_provider(oss_provider.as_deref(), config_toml_for_oss);
if let Some(provider) = resolved {
Some(provider)
@@ -560,6 +560,46 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
.await
}
#[allow(clippy::print_stderr)]
async fn load_config_toml_or_exit(
codex_home: &Path,
cwd: Option<&AbsolutePathBuf>,
cli_kv_overrides: Vec<(String, codex_config::TomlValue)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_config_bundle: CloudConfigBundleLoader,
) -> codex_config::config_toml::ConfigToml {
match load_config_as_toml_with_cli_and_load_options(
codex_home,
cwd,
cli_kv_overrides,
ConfigLoadOptions {
loader_overrides,
strict_config,
cloud_config_bundle,
},
)
.await
{
Ok(config_toml) => config_toml,
Err(err) => {
let config_error = err
.get_ref()
.and_then(|err| err.downcast_ref::<ConfigLoadError>())
.map(ConfigLoadError::config_error);
if let Some(config_error) = config_error {
eprintln!(
"Error loading config.toml:\n{}",
format_config_error_with_source(config_error)
);
} else {
eprintln!("Error loading config.toml: {err}");
}
std::process::exit(1);
}
}
}
async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
let ExecRunArgs {
in_process_start_args,

View File

@@ -971,51 +971,55 @@ pub async fn run_main(
loader_overrides.user_config_profile = Some(profile_v2.clone());
}
#[allow(clippy::print_stderr)]
let config_toml = match load_config_as_toml_with_cli_and_load_options(
let bootstrap_config_toml = load_config_toml_or_exit(
&codex_home,
config_cwd.as_ref(),
cli_kv_overrides.clone(),
codex_config::ConfigLoadOptions {
loader_overrides: loader_overrides.clone(),
strict_config,
},
loader_overrides.clone(),
strict_config,
CloudConfigBundleLoader::default(),
)
.await
{
Ok(config_toml) => config_toml,
Err(err) => {
let config_error = err
.get_ref()
.and_then(|err| err.downcast_ref::<ConfigLoadError>())
.map(ConfigLoadError::config_error);
if let Some(config_error) = config_error {
eprintln!(
"Error loading config.toml:\n{}",
format_config_error_with_source(config_error)
);
} else {
eprintln!("Error loading config.toml: {err}");
}
std::process::exit(1);
}
};
.await;
let chatgpt_base_url = config_toml
let chatgpt_base_url = bootstrap_config_toml
.chatgpt_base_url
.clone()
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string());
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
codex_home.to_path_buf(),
/*enable_codex_api_key_env*/ false,
config_toml.cli_auth_credentials_store.unwrap_or_default(),
bootstrap_config_toml
.cli_auth_credentials_store
.unwrap_or_default(),
chatgpt_base_url,
)
.await;
let cwd_override = if app_server_target.uses_remote_workspace() {
None
} else {
cwd.clone()
};
let config_toml_for_oss;
let config_toml_for_oss = if cli.oss && cli.oss_provider.is_none() {
config_toml_for_oss = load_config_toml_or_exit(
&codex_home,
config_cwd.as_ref(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
strict_config,
cloud_config_bundle.clone(),
)
.await;
&config_toml_for_oss
} else {
&bootstrap_config_toml
};
let mut manually_selected_oss_provider = None;
let model_provider_override = if cli.oss {
let resolved = resolve_oss_provider(cli.oss_provider.as_deref(), &config_toml);
let resolved = resolve_oss_provider(cli.oss_provider.as_deref(), config_toml_for_oss);
if let Some(provider) = resolved {
Some(provider)
@@ -1056,11 +1060,7 @@ pub async fn run_main(
model,
approval_policy,
sandbox_mode,
cwd: if app_server_target.uses_remote_workspace() {
None
} else {
cwd
},
cwd: cwd_override,
model_provider: model_provider_override.clone(),
codex_self_exe: arg0_paths.codex_self_exe.clone(),
codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe.clone(),
@@ -1150,6 +1150,11 @@ pub async fn run_main(
tracing::warn!(error = %err, "failed to deserialize config for personality migration");
}
}
let config_toml_log_dir_configured = config
.config_layer_stack
.effective_config()
.as_table()
.is_some_and(|table| table.contains_key("log_dir"));
#[allow(clippy::print_stderr)]
match check_execpolicy_for_warnings(&config.config_layer_stack).await {
@@ -1193,7 +1198,7 @@ pub async fn run_main(
}
}
let (tui_file_layer, _tui_file_log_guard) = if config_toml.log_dir.is_some() {
let (tui_file_layer, _tui_file_log_guard) = if config_toml_log_dir_configured {
let log_dir = config.log_dir.clone();
std::fs::create_dir_all(&log_dir)?;
let mut log_file_opts = OpenOptions::new();
@@ -1916,6 +1921,46 @@ async fn load_config_or_exit_with_fallback_cwd(
}
}
#[allow(clippy::print_stderr)]
async fn load_config_toml_or_exit(
codex_home: &Path,
cwd: Option<&AbsolutePathBuf>,
cli_kv_overrides: Vec<(String, codex_config::TomlValue)>,
loader_overrides: LoaderOverrides,
strict_config: bool,
cloud_config_bundle: CloudConfigBundleLoader,
) -> codex_config::config_toml::ConfigToml {
match load_config_as_toml_with_cli_and_load_options(
codex_home,
cwd,
cli_kv_overrides,
codex_config::ConfigLoadOptions {
loader_overrides,
strict_config,
cloud_config_bundle,
},
)
.await
{
Ok(config_toml) => config_toml,
Err(err) => {
let config_error = err
.get_ref()
.and_then(|err| err.downcast_ref::<ConfigLoadError>())
.map(ConfigLoadError::config_error);
if let Some(config_error) = config_error {
eprintln!(
"Error loading config.toml:\n{}",
format_config_error_with_source(config_error)
);
} else {
eprintln!("Error loading config.toml: {err}");
}
std::process::exit(1);
}
}
}
/// Determine if the user has decided whether to trust the current directory.
fn should_show_trust_screen(config: &Config) -> bool {
config.active_project.trust_level.is_none()