diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index a71abc4003ee..530bc7093d96 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -222,6 +222,7 @@ use codex_core::find_thread_name_by_id; use codex_core::find_thread_names_by_ids; use codex_core::find_thread_path_by_id_str; use codex_core::parse_cursor; +use codex_core::path_utils; use codex_core::plugins::MarketplaceError; use codex_core::plugins::MarketplacePluginSource; use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME; @@ -4768,9 +4769,9 @@ impl CodexMessageProcessor { if source_kind_filter .as_ref() .is_none_or(|filter| source_kind_matches(&summary.source, filter)) - && cwd - .as_ref() - .is_none_or(|expected_cwd| &summary.cwd == expected_cwd) + && cwd.as_ref().is_none_or(|expected_cwd| { + path_utils::paths_match_after_normalization(&summary.cwd, expected_cwd) + }) { filtered.push(summary); if filtered.len() >= remaining { diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/core/src/config/service.rs index 80d4b6bdd963..6c7f071f2b93 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/core/src/config/service.rs @@ -629,14 +629,7 @@ fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error> { } fn paths_match(expected: impl AsRef, provided: impl AsRef) -> bool { - if let (Ok(expanded_expected), Ok(expanded_provided)) = ( - path_utils::normalize_for_path_comparison(&expected), - path_utils::normalize_for_path_comparison(&provided), - ) { - expanded_expected == expanded_provided - } else { - expected.as_ref() == provided.as_ref() - } + path_utils::paths_match_after_normalization(expected, provided) } fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a TomlValue> { diff --git a/codex-rs/core/src/tools/runtimes/mod.rs b/codex-rs/core/src/tools/runtimes/mod.rs index 5ea14b8a8f43..795f0112d97b 100644 --- a/codex-rs/core/src/tools/runtimes/mod.rs +++ b/codex-rs/core/src/tools/runtimes/mod.rs @@ -76,14 +76,7 @@ pub(crate) fn maybe_wrap_shell_lc_with_snapshot( return command.to_vec(); } - if if let (Ok(snapshot_cwd), Ok(command_cwd)) = ( - path_utils::normalize_for_path_comparison(snapshot.cwd.as_path()), - path_utils::normalize_for_path_comparison(cwd), - ) { - snapshot_cwd != command_cwd - } else { - snapshot.cwd != cwd - } { + if !path_utils::paths_match_after_normalization(snapshot.cwd.as_path(), cwd) { return command.to_vec(); } diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 81a0961fa4ae..31099e8d6555 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -1205,13 +1205,7 @@ async fn parse_latest_turn_context_cwd(path: &Path) -> Option { } fn cwds_match(current_cwd: &Path, session_cwd: &Path) -> bool { - match ( - path_utils::normalize_for_path_comparison(current_cwd), - path_utils::normalize_for_path_comparison(session_cwd), - ) { - (Ok(current), Ok(session)) => current == session, - _ => current_cwd == session_cwd, - } + path_utils::paths_match_after_normalization(current_cwd, session_cwd) } async fn resolve_resume_thread_id( diff --git a/codex-rs/rollout/src/recorder.rs b/codex-rs/rollout/src/recorder.rs index a14214408975..01ea10cb958a 100644 --- a/codex-rs/rollout/src/recorder.rs +++ b/codex-rs/rollout/src/recorder.rs @@ -1342,13 +1342,7 @@ async fn select_resume_path_from_db_page( } fn cwd_matches(session_cwd: &Path, cwd: &Path) -> bool { - if let (Ok(ca), Ok(cb)) = ( - path_utils::normalize_for_path_comparison(session_cwd), - path_utils::normalize_for_path_comparison(cwd), - ) { - return ca == cb; - } - session_cwd == cwd + path_utils::paths_match_after_normalization(session_cwd, cwd) } #[cfg(test)] diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index cd5049499dfe..1e3b58452b2e 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1524,13 +1524,7 @@ async fn read_latest_turn_context(path: &Path) -> Option { } pub(crate) fn cwds_differ(current_cwd: &Path, session_cwd: &Path) -> bool { - match ( - path_utils::normalize_for_path_comparison(current_cwd), - path_utils::normalize_for_path_comparison(session_cwd), - ) { - (Ok(current), Ok(session)) => current != session, - _ => current_cwd != session_cwd, - } + !path_utils::paths_match_after_normalization(current_cwd, session_cwd) } pub(crate) enum ResolveCwdOutcome { diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index a3c076a5fb4e..d50d00bddaee 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -1148,13 +1148,7 @@ fn thread_list_params( } fn paths_match(a: &Path, b: &Path) -> bool { - if let (Ok(ca), Ok(cb)) = ( - path_utils::normalize_for_path_comparison(a), - path_utils::normalize_for_path_comparison(b), - ) { - return ca == cb; - } - a == b + path_utils::paths_match_after_normalization(a, b) } #[cfg_attr(not(test), allow(dead_code))] diff --git a/codex-rs/utils/path-utils/src/lib.rs b/codex-rs/utils/path-utils/src/lib.rs index 7e44e06d1377..5390250a1b41 100644 --- a/codex-rs/utils/path-utils/src/lib.rs +++ b/codex-rs/utils/path-utils/src/lib.rs @@ -16,6 +16,19 @@ pub fn normalize_for_path_comparison(path: impl AsRef) -> std::io::Result< Ok(normalize_for_wsl(canonical)) } +/// Compare paths after applying Codex's filesystem normalization. +/// +/// If either path cannot be normalized, this falls back to direct path equality. +pub fn paths_match_after_normalization(left: impl AsRef, right: impl AsRef) -> bool { + if let (Ok(left), Ok(right)) = ( + normalize_for_path_comparison(left.as_ref()), + normalize_for_path_comparison(right.as_ref()), + ) { + return left == right; + } + left.as_ref() == right.as_ref() +} + pub fn normalize_for_native_workdir(path: impl AsRef) -> PathBuf { normalize_for_native_workdir_with_flag(path.as_ref().to_path_buf(), cfg!(windows)) } diff --git a/codex-rs/utils/path-utils/src/path_utils_tests.rs b/codex-rs/utils/path-utils/src/path_utils_tests.rs index bda4b77ab3e4..94c914fec257 100644 --- a/codex-rs/utils/path-utils/src/path_utils_tests.rs +++ b/codex-rs/utils/path-utils/src/path_utils_tests.rs @@ -78,3 +78,38 @@ mod native_workdir { assert_eq!(normalized, path); } } + +mod path_comparison { + use super::super::paths_match_after_normalization; + use std::path::PathBuf; + + #[test] + fn matches_identical_existing_paths() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + assert!(paths_match_after_normalization(dir.path(), dir.path())); + Ok(()) + } + + #[test] + fn falls_back_to_raw_equality_when_paths_cannot_be_normalized() { + assert!(paths_match_after_normalization( + PathBuf::from("missing"), + PathBuf::from("missing"), + )); + assert!(!paths_match_after_normalization( + PathBuf::from("missing-a"), + PathBuf::from("missing-b"), + )); + } + + #[cfg(windows)] + #[test] + fn matches_windows_verbatim_paths() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + let verbatim_dir = PathBuf::from(format!(r"\\?\{}", dir.path().display())); + + assert!(paths_match_after_normalization(verbatim_dir, dir.path())); + Ok(()) + } +}