Skip to content

Commit bd61737

Browse files
authored
Async config loading (#18022)
Parts of config will come from executor. Prepare for that by making config loading methods async.
1 parent d97bad1 commit bd61737

25 files changed

Lines changed: 624 additions & 471 deletions

File tree

codex-rs/app-server-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ mod tests {
971971
match ConfigBuilder::default().build().await {
972972
Ok(config) => config,
973973
Err(_) => Config::load_default_with_cli_overrides(Vec::new())
974+
.await
974975
.expect("default config should load"),
975976
}
976977
}

codex-rs/app-server/src/codex_message_processor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,7 @@ impl CodexMessageProcessor {
24422442
))
24432443
{
24442444
let trust_target = resolve_root_git_project_for_trust(config.cwd.as_path())
2445+
.await
24452446
.unwrap_or_else(|| config.cwd.to_path_buf());
24462447
let cli_overrides_with_trust;
24472448
let cli_overrides_for_reload = if let Err(err) =

codex-rs/app-server/src/in_process.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ mod tests {
716716
match ConfigBuilder::default().build().await {
717717
Ok(config) => config,
718718
Err(_) => Config::load_default_with_cli_overrides(Vec::new())
719+
.await
719720
.expect("default config should load"),
720721
}
721722
}

codex-rs/app-server/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,12 +432,14 @@ pub async fn run_main_with_transport(
432432
Err(err) => {
433433
let message = config_warning_from_error("Invalid configuration; using defaults.", &err);
434434
config_warnings.push(message);
435-
Config::load_default_with_cli_overrides(cli_kv_overrides.clone()).map_err(|e| {
436-
std::io::Error::new(
437-
ErrorKind::InvalidData,
438-
format!("error loading default config after config error: {e}"),
439-
)
440-
})?
435+
Config::load_default_with_cli_overrides(cli_kv_overrides.clone())
436+
.await
437+
.map_err(|e| {
438+
std::io::Error::new(
439+
ErrorKind::InvalidData,
440+
format!("error loading default config after config error: {e}"),
441+
)
442+
})?
441443
}
442444
};
443445

codex-rs/app-server/tests/suite/v2/thread_start.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ model_reasoning_effort = "high"
717717

718718
let config_toml = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
719719
let trusted_root = resolve_root_git_project_for_trust(workspace.path())
720+
.await
720721
.unwrap_or_else(|| workspace.path().to_path_buf());
721722
assert!(config_toml.contains(&persisted_trust_path(&trusted_root)));
722723
assert!(config_toml.contains("trust_level = \"trusted\""));
@@ -753,8 +754,9 @@ async fn thread_start_with_nested_git_cwd_trusts_repo_root() -> Result<()> {
753754
.await??;
754755

755756
let config_toml = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
756-
let trusted_root =
757-
resolve_root_git_project_for_trust(&nested).expect("git root should resolve");
757+
let trusted_root = resolve_root_git_project_for_trust(&nested)
758+
.await
759+
.expect("git root should resolve");
758760
assert!(config_toml.contains(&persisted_trust_path(&trusted_root)));
759761
assert!(!config_toml.contains(&persisted_trust_path(&nested)));
760762

codex-rs/config/src/config_toml.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ pub struct GhostSnapshotToml {
596596

597597
impl ConfigToml {
598598
/// Derive the effective sandbox policy from the configuration.
599-
pub fn derive_sandbox_policy(
599+
pub async fn derive_sandbox_policy(
600600
&self,
601601
sandbox_mode_override: Option<SandboxMode>,
602602
profile_sandbox_mode: Option<SandboxMode>,
@@ -610,11 +610,13 @@ impl ConfigToml {
610610
let resolved_sandbox_mode = sandbox_mode_override
611611
.or(profile_sandbox_mode)
612612
.or(self.sandbox_mode)
613-
.or_else(|| {
613+
.or(if sandbox_mode_was_explicit {
614+
None
615+
} else {
614616
// If no sandbox_mode is set but this directory has a trust decision,
615617
// default to workspace-write except on unsandboxed Windows where we
616618
// default to read-only.
617-
self.get_active_project(resolved_cwd).and_then(|p| {
619+
self.get_active_project(resolved_cwd).await.and_then(|p| {
618620
if p.is_trusted() || p.is_untrusted() {
619621
if cfg!(target_os = "windows")
620622
&& windows_sandbox_level == WindowsSandboxLevel::Disabled
@@ -676,7 +678,8 @@ impl ConfigToml {
676678

677679
/// Resolves the cwd to an existing project, or returns None if ConfigToml
678680
/// does not contain a project corresponding to cwd or a git repo for cwd
679-
pub fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
681+
pub async fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
682+
let repo_root = resolve_root_git_project_for_trust(resolved_cwd).await;
680683
let projects = self.projects.clone().unwrap_or_default();
681684

682685
let resolved_cwd_key = project_trust_key(resolved_cwd);
@@ -691,8 +694,8 @@ impl ConfigToml {
691694
// If cwd lives inside a git repo/worktree, check whether the root git project
692695
// (the primary repository working directory) is trusted. This lets
693696
// worktrees inherit trust from the main project.
694-
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd) {
695-
let repo_root_key = project_trust_key(repo_root.as_path());
697+
if let Some(repo_root) = repo_root.as_deref() {
698+
let repo_root_key = project_trust_key(repo_root);
696699
let repo_root_raw_key = repo_root.to_string_lossy().to_string();
697700
if let Some(project_config_for_root) = projects
698701
.get(&repo_root_key)

codex-rs/core/src/agent/role.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ async fn apply_role_to_config_inner(
7878
role_layer_toml,
7979
preserve_current_profile,
8080
preserve_current_provider,
81-
)?;
81+
)
82+
.await?;
8283
Ok(())
8384
}
8485

@@ -150,7 +151,7 @@ fn preservation_policy(config: &Config, role_layer_toml: &TomlValue) -> (bool, b
150151
mod reload {
151152
use super::*;
152153

153-
pub(super) fn build_next_config(
154+
pub(super) async fn build_next_config(
154155
config: &Config,
155156
role_layer_toml: TomlValue,
156157
preserve_current_profile: bool,
@@ -171,7 +172,8 @@ mod reload {
171172
reload_overrides(config, preserve_current_provider),
172173
config.codex_home.clone(),
173174
config_layer_stack,
174-
)?;
175+
)
176+
.await?;
175177
if preserve_current_profile {
176178
next_config.active_profile = config.active_profile.clone();
177179
}

codex-rs/core/src/codex_tests.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ fn numbered_mcp_tools(count: usize) -> HashMap<String, ToolInfo> {
457457
.collect()
458458
}
459459

460-
fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
461-
let config = test_config();
460+
async fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
461+
let config = test_config().await;
462462
let model_info = ModelsManager::construct_model_info_offline_for_tests(
463463
"gpt-5-codex",
464464
&config.to_models_manager_config(),
@@ -870,7 +870,7 @@ async fn get_base_instructions_no_user_content() {
870870
];
871871

872872
let (session, _turn_context) = make_session_and_context().await;
873-
let config = test_config();
873+
let config = test_config().await;
874874

875875
for test_case in test_cases {
876876
let model_info = model_info_for_slug(test_case.slug, &config);
@@ -1035,10 +1035,10 @@ fn collect_explicit_app_ids_from_skill_items_skips_plain_mentions_with_skill_con
10351035
assert_eq!(connector_ids, HashSet::<String>::new());
10361036
}
10371037

1038-
#[test]
1039-
fn mcp_tool_exposure_directly_exposes_small_effective_tool_sets() {
1040-
let config = test_config();
1041-
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true);
1038+
#[tokio::test]
1039+
async fn mcp_tool_exposure_directly_exposes_small_effective_tool_sets() {
1040+
let config = test_config().await;
1041+
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
10421042
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1);
10431043

10441044
let exposure = build_mcp_tool_exposure(
@@ -1057,10 +1057,10 @@ fn mcp_tool_exposure_directly_exposes_small_effective_tool_sets() {
10571057
assert!(exposure.deferred_tools.is_none());
10581058
}
10591059

1060-
#[test]
1061-
fn mcp_tool_exposure_searches_large_effective_tool_sets() {
1062-
let config = test_config();
1063-
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true);
1060+
#[tokio::test]
1061+
async fn mcp_tool_exposure_searches_large_effective_tool_sets() {
1062+
let config = test_config().await;
1063+
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
10641064
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);
10651065

10661066
let exposure = build_mcp_tool_exposure(
@@ -1083,10 +1083,10 @@ fn mcp_tool_exposure_searches_large_effective_tool_sets() {
10831083
assert_eq!(deferred_tool_names, expected_tool_names);
10841084
}
10851085

1086-
#[test]
1087-
fn mcp_tool_exposure_directly_exposes_explicit_apps_without_deferred_overlap() {
1088-
let config = test_config();
1089-
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true);
1086+
#[tokio::test]
1087+
async fn mcp_tool_exposure_directly_exposes_explicit_apps_without_deferred_overlap() {
1088+
let config = test_config().await;
1089+
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
10901090
let mut mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1);
10911091
mcp_tools.extend([(
10921092
"mcp__codex_apps__calendar_create_event".to_string(),

0 commit comments

Comments
 (0)