From f60b2cc5b183b56dc1f80bfc674e954eae2c215e Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Tue, 14 Apr 2026 11:47:29 -0700 Subject: [PATCH] Extract shared project root resolution helper --- codex-rs/core/src/config_loader/mod.rs | 26 ++++++++++++++ codex-rs/core/src/hook_runtime.rs | 36 ++++--------------- codex-rs/core/src/project_doc.rs | 50 +++----------------------- 3 files changed, 37 insertions(+), 75 deletions(-) diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 7ca53ff833..a7904a3cbf 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -25,6 +25,7 @@ use std::path::Path; #[cfg(windows)] use std::path::PathBuf; use toml::Value as TomlValue; +use tracing::warn; pub use codex_config::AppRequirementToml; pub use codex_config::AppsRequirementsToml; @@ -760,6 +761,31 @@ pub(crate) async fn find_project_root( Ok(cwd.clone()) } +pub(crate) async fn resolve_project_root( + config_layer_stack: &ConfigLayerStack, + cwd: &AbsolutePathBuf, +) -> io::Result { + let mut merged = TomlValue::Table(toml::map::Map::new()); + for layer in config_layer_stack.get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ false, + ) { + if matches!(layer.name, ConfigLayerSource::Project { .. }) { + continue; + } + merge_toml_values(&mut merged, &layer.config); + } + let project_root_markers = match project_root_markers_from_config(&merged) { + Ok(Some(markers)) => markers, + Ok(None) => default_project_root_markers(), + Err(err) => { + warn!("invalid project_root_markers: {err}"); + default_project_root_markers() + } + }; + find_project_root(cwd, &project_root_markers).await +} + /// Return the appropriate list of layers (each with /// [ConfigLayerSource::Project] as the source) between `cwd` and /// `project_root`, inclusive. The list is ordered in _increasing_ precdence, diff --git a/codex-rs/core/src/hook_runtime.rs b/codex-rs/core/src/hook_runtime.rs index 8d17855f77..449d492462 100644 --- a/codex-rs/core/src/hook_runtime.rs +++ b/codex-rs/core/src/hook_runtime.rs @@ -1,7 +1,6 @@ use std::future::Future; use std::sync::Arc; -use codex_app_server_protocol::ConfigLayerSource; use codex_hooks::PermissionRequestDecision; use codex_hooks::PermissionRequestOutcome; use codex_hooks::PermissionRequestRequest; @@ -30,17 +29,12 @@ use codex_protocol::protocol::HookStartedEvent; use codex_protocol::protocol::WarningEvent; use codex_protocol::user_input::UserInput; use serde_json::Value; -use toml::Value as TomlValue; use tracing::warn; use crate::codex::Session; use crate::codex::TurnContext; use crate::config::Config; -use crate::config_loader::ConfigLayerStackOrdering; -use crate::config_loader::default_project_root_markers; -use crate::config_loader::find_project_root; -use crate::config_loader::merge_toml_values; -use crate::config_loader::project_root_markers_from_config; +use crate::config_loader::resolve_project_root; use crate::event_mapping::parse_turn_item; use crate::tools::sandboxing::PermissionRequestPayload; @@ -289,28 +283,12 @@ async fn apply_execpolicy_amendment_destination( } async fn project_codex_home(config: &Config) -> std::io::Result { - let mut merged = TomlValue::Table(toml::map::Map::new()); - for layer in config.config_layer_stack.get_layers( - ConfigLayerStackOrdering::LowestPrecedenceFirst, - /*include_disabled*/ false, - ) { - if matches!(layer.name, ConfigLayerSource::Project { .. }) { - continue; - } - merge_toml_values(&mut merged, &layer.config); - } - let project_root_markers = match project_root_markers_from_config(&merged) { - Ok(Some(markers)) => markers, - Ok(None) => default_project_root_markers(), - Err(err) => { - warn!("invalid project_root_markers: {err}"); - default_project_root_markers() - } - }; - Ok(find_project_root(&config.cwd, &project_root_markers) - .await? - .join(".codex") - .to_path_buf()) + Ok( + resolve_project_root(&config.config_layer_stack, &config.cwd) + .await? + .join(".codex") + .to_path_buf(), + ) } pub(crate) async fn run_post_tool_use_hooks( diff --git a/codex-rs/core/src/project_doc.rs b/codex-rs/core/src/project_doc.rs index e7321695a7..a734f76200 100644 --- a/codex-rs/core/src/project_doc.rs +++ b/codex-rs/core/src/project_doc.rs @@ -16,18 +16,13 @@ //! 3. We do **not** walk past the project root. use crate::config::Config; -use crate::config_loader::ConfigLayerStackOrdering; -use crate::config_loader::default_project_root_markers; -use crate::config_loader::merge_toml_values; -use crate::config_loader::project_root_markers_from_config; -use codex_app_server_protocol::ConfigLayerSource; +use crate::config_loader::resolve_project_root; use codex_exec_server::Environment; use codex_exec_server::ExecutorFileSystem; use codex_features::Feature; use codex_utils_absolute_path::AbsolutePathBuf; use dunce::canonicalize as normalize_path; use std::io; -use toml::Value as TomlValue; use tracing::error; pub(crate) const HIERARCHICAL_AGENTS_MESSAGE: &str = @@ -226,51 +221,14 @@ pub async fn discover_project_doc_paths( dir = AbsolutePathBuf::try_from(canon)?; } - let mut merged = TomlValue::Table(toml::map::Map::new()); - for layer in config.config_layer_stack.get_layers( - ConfigLayerStackOrdering::LowestPrecedenceFirst, - /*include_disabled*/ false, - ) { - if matches!(layer.name, ConfigLayerSource::Project { .. }) { - continue; - } - merge_toml_values(&mut merged, &layer.config); - } - let project_root_markers = match project_root_markers_from_config(&merged) { - Ok(Some(markers)) => markers, - Ok(None) => default_project_root_markers(), - Err(err) => { - tracing::warn!("invalid project_root_markers: {err}"); - default_project_root_markers() - } - }; - let mut project_root = None; - if !project_root_markers.is_empty() { - for ancestor in dir.ancestors() { - for marker in &project_root_markers { - let marker_path = AbsolutePathBuf::try_from(ancestor.join(marker))?; - let marker_exists = match fs.get_metadata(&marker_path).await { - Ok(_) => true, - Err(err) if err.kind() == io::ErrorKind::NotFound => false, - Err(err) => return Err(err), - }; - if marker_exists { - project_root = Some(AbsolutePathBuf::try_from(ancestor.to_path_buf())?); - break; - } - } - if project_root.is_some() { - break; - } - } - } + let project_root = resolve_project_root(&config.config_layer_stack, &dir).await?; - let search_dirs: Vec = if let Some(root) = project_root { + let search_dirs: Vec = if project_root != dir { let mut dirs = Vec::new(); let mut cursor = dir.clone(); loop { dirs.push(cursor.clone()); - if cursor == root { + if cursor == project_root { break; } let Some(parent) = cursor.parent() else {