mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Fixed resume matching to respect case insensitivity when using WSL mount points (#8000)
This fixes #7995
This commit is contained in:
@@ -75,6 +75,7 @@ If you don’t have the tool:
|
||||
|
||||
- Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already.
|
||||
- Prefer deep equals comparisons whenever possible. Perform `assert_eq!()` on entire objects, rather than individual fields.
|
||||
- Avoid mutating process environment in tests; prefer passing environment-derived flags or dependencies from above.
|
||||
|
||||
### Integration tests (core)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::config_loader::ConfigLayerStack;
|
||||
use crate::config_loader::LoaderOverrides;
|
||||
use crate::config_loader::load_config_layers_state;
|
||||
use crate::config_loader::merge_toml_values;
|
||||
use crate::path_utils;
|
||||
use codex_app_server_protocol::Config as ApiConfig;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
use codex_app_server_protocol::ConfigLayerMetadata;
|
||||
@@ -470,9 +471,10 @@ fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error> {
|
||||
}
|
||||
|
||||
fn paths_match(expected: &Path, provided: &Path) -> bool {
|
||||
if let (Ok(expanded_expected), Ok(expanded_provided)) =
|
||||
(expected.canonicalize(), provided.canonicalize())
|
||||
{
|
||||
if let (Ok(expanded_expected), Ok(expanded_provided)) = (
|
||||
path_utils::normalize_for_path_comparison(expected),
|
||||
path_utils::normalize_for_path_comparison(provided),
|
||||
) {
|
||||
return expanded_expected == expanded_provided;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ mod mcp_tool_call;
|
||||
mod message_history;
|
||||
mod model_provider_info;
|
||||
pub mod parse_command;
|
||||
pub mod path_utils;
|
||||
pub mod powershell;
|
||||
pub mod sandboxing;
|
||||
mod stream_events_utils;
|
||||
|
||||
116
codex-rs/core/src/path_utils.rs
Normal file
116
codex-rs/core/src/path_utils.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::env;
|
||||
|
||||
pub fn normalize_for_path_comparison(path: &Path) -> std::io::Result<PathBuf> {
|
||||
let canonical = path.canonicalize()?;
|
||||
Ok(normalize_for_wsl(canonical))
|
||||
}
|
||||
|
||||
fn normalize_for_wsl(path: PathBuf) -> PathBuf {
|
||||
normalize_for_wsl_with_flag(path, env::is_wsl())
|
||||
}
|
||||
|
||||
fn normalize_for_wsl_with_flag(path: PathBuf, is_wsl: bool) -> PathBuf {
|
||||
if !is_wsl {
|
||||
return path;
|
||||
}
|
||||
|
||||
if !is_wsl_case_insensitive_path(&path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
lower_ascii_path(path)
|
||||
}
|
||||
|
||||
fn is_wsl_case_insensitive_path(path: &Path) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Component;
|
||||
|
||||
let mut components = path.components();
|
||||
let Some(Component::RootDir) = components.next() else {
|
||||
return false;
|
||||
};
|
||||
let Some(Component::Normal(mnt)) = components.next() else {
|
||||
return false;
|
||||
};
|
||||
if !ascii_eq_ignore_case(mnt.as_bytes(), b"mnt") {
|
||||
return false;
|
||||
}
|
||||
let Some(Component::Normal(drive)) = components.next() else {
|
||||
return false;
|
||||
};
|
||||
let drive_bytes = drive.as_bytes();
|
||||
drive_bytes.len() == 1 && drive_bytes[0].is_ascii_alphabetic()
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = path;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn ascii_eq_ignore_case(left: &[u8], right: &[u8]) -> bool {
|
||||
left.len() == right.len()
|
||||
&& left
|
||||
.iter()
|
||||
.zip(right)
|
||||
.all(|(lhs, rhs)| lhs.to_ascii_lowercase() == *rhs)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn lower_ascii_path(path: PathBuf) -> PathBuf {
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
// WSL mounts Windows drives under /mnt/<drive>, which are case-insensitive.
|
||||
let bytes = path.as_os_str().as_bytes();
|
||||
let mut lowered = Vec::with_capacity(bytes.len());
|
||||
for byte in bytes {
|
||||
lowered.push(byte.to_ascii_lowercase());
|
||||
}
|
||||
PathBuf::from(OsString::from_vec(lowered))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn lower_ascii_path(path: PathBuf) -> PathBuf {
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wsl {
|
||||
use super::super::normalize_for_wsl_with_flag;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn wsl_mnt_drive_paths_lowercase() {
|
||||
let normalized = normalize_for_wsl_with_flag(PathBuf::from("/mnt/C/Users/Dev"), true);
|
||||
|
||||
assert_eq!(normalized, PathBuf::from("/mnt/c/users/dev"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wsl_non_drive_paths_unchanged() {
|
||||
let path = PathBuf::from("/mnt/cc/Users/Dev");
|
||||
let normalized = normalize_for_wsl_with_flag(path.clone(), true);
|
||||
|
||||
assert_eq!(normalized, path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wsl_non_mnt_paths_unchanged() {
|
||||
let path = PathBuf::from("/home/Dev");
|
||||
let normalized = normalize_for_wsl_with_flag(path.clone(), true);
|
||||
|
||||
assert_eq!(normalized, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use codex_core::ConversationsPage;
|
||||
use codex_core::Cursor;
|
||||
use codex_core::INTERACTIVE_SESSION_SOURCES;
|
||||
use codex_core::RolloutRecorder;
|
||||
use codex_core::path_utils;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -670,7 +671,10 @@ fn extract_session_meta_from_head(head: &[serde_json::Value]) -> (Option<PathBuf
|
||||
}
|
||||
|
||||
fn paths_match(a: &Path, b: &Path) -> bool {
|
||||
if let (Ok(ca), Ok(cb)) = (a.canonicalize(), b.canonicalize()) {
|
||||
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
|
||||
|
||||
@@ -10,6 +10,7 @@ use codex_core::ConversationsPage;
|
||||
use codex_core::Cursor;
|
||||
use codex_core::INTERACTIVE_SESSION_SOURCES;
|
||||
use codex_core::RolloutRecorder;
|
||||
use codex_core::path_utils;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -670,7 +671,10 @@ fn extract_session_meta_from_head(head: &[serde_json::Value]) -> (Option<PathBuf
|
||||
}
|
||||
|
||||
fn paths_match(a: &Path, b: &Path) -> bool {
|
||||
if let (Ok(ca), Ok(cb)) = (a.canonicalize(), b.canonicalize()) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user