From 5b47d505b8ee7682ee53291d1452071e9234cf0b Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Fri, 10 Apr 2026 20:43:40 -0700 Subject: [PATCH] Fix permission profile CI failures Preserve the Linux sandbox helper alias in core tests when it is already available, and compare persisted filesystem permission paths using platform-neutral config strings so Windows CI handles POSIX-style test paths correctly. Co-authored-by: Codex --- codex-rs/core/src/config/edit.rs | 71 ++++++++++++++++-------- codex-rs/core/tests/common/test_codex.rs | 4 +- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index 3c97332049..a930e6802e 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -529,11 +529,9 @@ impl ConfigDocument { return false; }; - let path = Path::new(path); for (key, item) in filesystem.iter() { if matches!(item.as_str(), Some("write")) { - let candidate = Path::new(key); - if path.starts_with(candidate) && path != candidate { + if is_strict_descendant_config_path(path, key) { return true; } continue; @@ -542,8 +540,7 @@ impl ConfigDocument { let Some(scoped_entries) = item.as_table() else { continue; }; - let base = Path::new(key); - if !base.is_absolute() { + if !is_absolute_config_path(key) { continue; } @@ -552,12 +549,8 @@ impl ConfigDocument { continue; } - let candidate = if subpath == "." { - base.to_path_buf() - } else { - base.join(subpath) - }; - if path.starts_with(&candidate) && path != candidate { + let candidate = join_config_path(key, subpath); + if is_strict_descendant_config_path(path, &candidate) { return true; } } @@ -575,13 +568,11 @@ impl ConfigDocument { return false; }; - let path = Path::new(path); let mut descendants_to_remove = Vec::new(); let mut scoped_parents_to_prune = Vec::new(); for (key, item) in filesystem.iter() { if matches!(item.as_str(), Some("read")) { - let candidate = Path::new(key); - if candidate.starts_with(path) && candidate != path { + if is_strict_descendant_config_path(key, path) { descendants_to_remove.push(vec![key.to_string()]); } continue; @@ -590,8 +581,7 @@ impl ConfigDocument { let Some(scoped_entries) = item.as_table() else { continue; }; - let base = Path::new(key); - if !base.is_absolute() { + if !is_absolute_config_path(key) { continue; } @@ -600,12 +590,8 @@ impl ConfigDocument { continue; } - let candidate = if subpath == "." { - base.to_path_buf() - } else { - base.join(subpath) - }; - if candidate.starts_with(path) && candidate != path { + let candidate = join_config_path(key, subpath); + if is_strict_descendant_config_path(&candidate, path) { descendants_to_remove.push(vec![key.to_string(), subpath.to_string()]); scoped_parents_to_prune.push(key.to_string()); } @@ -918,6 +904,47 @@ fn normalize_skill_config_path(path: &Path) -> String { .to_string() } +fn is_absolute_config_path(path: &str) -> bool { + path.starts_with('/') + || path.starts_with('\\') + || path.as_bytes().get(0..3).is_some_and(|prefix| { + prefix[0].is_ascii_alphabetic() && prefix[1] == b':' && is_separator(prefix[2]) + }) +} + +fn is_separator(byte: u8) -> bool { + byte == b'/' || byte == b'\\' +} + +fn join_config_path(base: &str, subpath: &str) -> String { + if subpath == "." { + return base.to_string(); + } + + format!( + "{}/{}", + base.trim_end_matches(['/', '\\']), + subpath.trim_start_matches(['/', '\\']) + ) +} + +fn is_strict_descendant_config_path(path: &str, ancestor: &str) -> bool { + let path = normalize_config_path(path); + let ancestor = normalize_config_path(ancestor); + path != ancestor + && path + .strip_prefix(&ancestor) + .is_some_and(|suffix| suffix.starts_with('/')) +} + +fn normalize_config_path(path: &str) -> String { + let mut normalized = path.replace('\\', "/"); + while normalized.len() > 1 && normalized.ends_with('/') { + normalized.pop(); + } + normalized +} + fn filesystem_path_access( file_system: Option<&FileSystemPermissions>, ) -> BTreeMap { diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index f9aa6a5f34..a69f7ea267 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -619,7 +619,9 @@ impl TestCodexBuilder { config.codex_self_exe = Some(codex_exec); } } - if let Some(path) = find_codex_cli_exe() { + if config.codex_linux_sandbox_exe.is_none() + && let Some(path) = find_codex_cli_exe() + { config.codex_linux_sandbox_exe = Some(path); }