diff --git a/codex-rs/core-skills/src/loader_tests.rs b/codex-rs/core-skills/src/loader_tests.rs index c80585871e..c03e8349e2 100644 --- a/codex-rs/core-skills/src/loader_tests.rs +++ b/codex-rs/core-skills/src/loader_tests.rs @@ -44,7 +44,7 @@ fn project_layers_for_cwd(cwd: &Path) -> Vec { }; let project_root = cwd_dir .ancestors() - .find(|ancestor| ancestor.join(".git").exists()) + .find(|ancestor| ancestor.join(".git").exists() && !is_ambient_git_marker_dir(ancestor)) .unwrap_or(cwd_dir.as_path()) .to_path_buf(); diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 1723904cff..53f377380b 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -69,6 +69,7 @@ use codex_analytics::build_track_events_context; use codex_async_utils::OrCancelExt; use codex_features::Feature; use codex_git_utils::get_git_repo_root; +use codex_git_utils::get_git_repo_root_with_fs; use codex_hooks::HookEvent; use codex_hooks::HookEventAfterAgent; use codex_hooks::HookPayload; @@ -100,6 +101,7 @@ use codex_protocol::protocol::WarningEvent; use codex_protocol::user_input::UserInput; use codex_tools::ToolName; use codex_tools::filter_request_plugin_install_discoverable_tools_for_client; +use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_stream_parser::AssistantTextChunk; use codex_utils_stream_parser::AssistantTextStreamParser; use codex_utils_stream_parser::ProposedPlanSegment; @@ -366,8 +368,13 @@ pub(crate) async fn run_turn( let mut stop_hook_active = false; // Although from the perspective of codex.rs, TurnDiffTracker has the lifecycle of a Task which contains // many turns, from the perspective of the user, it is a single turn. - let display_root = get_git_repo_root(turn_context.cwd.as_path()) - .unwrap_or_else(|| turn_context.cwd.clone().into_path_buf()); + let display_root = match turn_context.environments.primary_filesystem() { + Some(fs) => get_git_repo_root_with_fs(fs.as_ref(), &turn_context.cwd) + .await + .map(AbsolutePathBuf::into_path_buf), + None => get_git_repo_root(turn_context.cwd.as_path()), + } + .unwrap_or_else(|| turn_context.cwd.clone().into_path_buf()); let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::with_display_root( display_root, ))); diff --git a/codex-rs/git-utils/src/info.rs b/codex-rs/git-utils/src/info.rs index 556c06868a..81bc46b64f 100644 --- a/codex-rs/git-utils/src/info.rs +++ b/codex-rs/git-utils/src/info.rs @@ -38,6 +38,21 @@ pub fn get_git_repo_root(base_dir: &Path) -> Option { find_ancestor_git_entry(base).map(|(repo_root, _)| repo_root) } +/// Return the git repository root for `base_dir` using the provided executor +/// filesystem. This is the remote-environment equivalent of [`get_git_repo_root`]. +pub async fn get_git_repo_root_with_fs( + fs: &dyn ExecutorFileSystem, + base_dir: &AbsolutePathBuf, +) -> Option { + let base = match fs.get_metadata(base_dir, /*sandbox*/ None).await { + Ok(metadata) if metadata.is_directory => base_dir.clone(), + _ => base_dir.parent()?, + }; + find_ancestor_git_entry_with_fs(fs, &base) + .await + .map(|(repo_root, _)| repo_root) +} + /// Timeout for git commands to prevent freezing on large repositories const GIT_COMMAND_TIMEOUT: TokioDuration = TokioDuration::from_secs(5); diff --git a/codex-rs/git-utils/src/lib.rs b/codex-rs/git-utils/src/lib.rs index bcd1a8b5c9..986a7434d2 100644 --- a/codex-rs/git-utils/src/lib.rs +++ b/codex-rs/git-utils/src/lib.rs @@ -31,6 +31,7 @@ pub use info::default_branch_name; pub use info::get_git_remote_urls; pub use info::get_git_remote_urls_assume_git_repo; pub use info::get_git_repo_root; +pub use info::get_git_repo_root_with_fs; pub use info::get_has_changes; pub use info::get_head_commit_hash; pub use info::git_diff_to_remote;