Fixed resume matching to respect case insensitivity when using WSL mount points (#8000)

This fixes #7995
This commit is contained in:
Eric Traut
2025-12-16 18:27:38 -06:00
committed by GitHub
Unverified
parent 14d80c35a9
commit 42b8f28ee8
6 changed files with 133 additions and 5 deletions
+5 -3
View File
@@ -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;
}
+1
View File
@@ -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
View 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);
}
}
}