From 10b0b15fa5bc096e9c282092736da1833f66ea70 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 19 Feb 2026 15:59:09 -0800 Subject: [PATCH] tui: canonicalize skill error lookup for symlinked cwd --- codex-rs/tui/src/app.rs | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 323769dab3..b5434d2385 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -158,10 +158,23 @@ fn session_summary( } fn errors_for_cwd(cwd: &Path, response: &ListSkillsResponseEvent) -> Vec { + let cwd_canonical = dunce::canonicalize(cwd).ok(); response .skills .iter() - .find(|entry| entry.cwd.as_path() == cwd) + .find(|entry| { + if entry.cwd.as_path() == cwd { + return true; + } + + let Some(cwd_canonical) = cwd_canonical.as_ref() else { + return false; + }; + let Ok(entry_canonical) = dunce::canonicalize(&entry.cwd) else { + return false; + }; + entry_canonical == *cwd_canonical + }) .map(|entry| entry.errors.clone()) .unwrap_or_default() } @@ -2947,6 +2960,36 @@ mod tests { Ok(()) } + #[cfg(unix)] + #[test] + fn errors_for_cwd_matches_symlinked_paths() { + let temp_dir = tempdir().expect("tempdir"); + let real_dir = temp_dir.path().join("real"); + let link_dir = temp_dir.path().join("link"); + std::fs::create_dir_all(&real_dir).expect("create real dir"); + std::os::unix::fs::symlink(&real_dir, &link_dir).expect("create symlink"); + + let expected_error = SkillErrorInfo { + path: real_dir.join("SKILL.md"), + message: "invalid frontmatter".to_string(), + }; + let response = ListSkillsResponseEvent { + skills: vec![codex_core::protocol::SkillsListEntry { + cwd: real_dir, + skills: Vec::new(), + errors: vec![expected_error.clone()], + }], + }; + + let actual_errors = errors_for_cwd(&link_dir, &response); + let actual = actual_errors + .into_iter() + .map(|error| (error.path, error.message)) + .collect::>(); + let expected = vec![(expected_error.path, expected_error.message)]; + assert_eq!(actual, expected); + } + #[test] fn startup_waiting_gate_is_only_for_fresh_or_exit_session_selection() { assert_eq!(