From 2e23ca8650392fc5e1f1c2db72be38c162c0aa32 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Sat, 28 Mar 2026 04:41:10 -0700 Subject: [PATCH] fix: tolerate windows trust path aliases Co-authored-by: Codex noreply@openai.com --- codex-rs/core/src/config/mod.rs | 49 +++++++++-- codex-rs/core/src/config_loader/mod.rs | 109 ++++++++++++++++++++----- 2 files changed, 127 insertions(+), 31 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 1a0722119b..99c914a1cb 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -85,11 +85,11 @@ use codex_protocol::permissions::NetworkSandboxPolicy; use codex_rmcp_client::OAuthCredentialsStoreMode; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; +use dunce::canonicalize as normalize_path; use schemars::JsonSchema; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; -use similar::DiffableStr; use std::collections::BTreeMap; use std::collections::HashMap; use std::io::ErrorKind; @@ -1696,25 +1696,56 @@ impl ConfigToml { /// Resolves the cwd to an existing project, or returns None if ConfigToml /// does not contain a project corresponding to cwd or a git repo for cwd pub fn get_active_project(&self, resolved_cwd: &Path) -> Option { - let projects = self.projects.clone().unwrap_or_default(); + let mut projects = HashMap::new(); + for (key, project_config) in self.projects.clone().unwrap_or_default() { + for normalized_key in Self::normalized_project_lookup_keys(Path::new(&key)) { + projects.insert(normalized_key, project_config.clone()); + } + } - if let Some(project_config) = projects.get(&resolved_cwd.to_string_lossy().to_string()) { - return Some(project_config.clone()); + for normalized_cwd in Self::normalized_project_lookup_keys(resolved_cwd) { + if let Some(project_config) = projects.get(&normalized_cwd) { + return Some(project_config.clone()); + } } // If cwd lives inside a git repo/worktree, check whether the root git project // (the primary repository working directory) is trusted. This lets // worktrees inherit trust from the main project. - if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd) - && let Some(project_config_for_root) = - projects.get(&repo_root.to_string_lossy().to_string_lossy().to_string()) - { - return Some(project_config_for_root.clone()); + if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd) { + for normalized_repo_root in Self::normalized_project_lookup_keys(&repo_root) { + if let Some(project_config_for_root) = projects.get(&normalized_repo_root) { + return Some(project_config_for_root.clone()); + } + } } None } + fn normalized_project_lookup_keys(path: &Path) -> Vec { + let normalized_path = + Self::normalize_project_lookup_key_string(path.to_string_lossy().to_string()); + let normalized_canonical_path = Self::normalize_project_lookup_key_string( + normalize_path(path) + .unwrap_or_else(|_| path.to_path_buf()) + .to_string_lossy() + .to_string(), + ); + if normalized_path == normalized_canonical_path { + vec![normalized_path] + } else { + vec![normalized_path, normalized_canonical_path] + } + } + + fn normalize_project_lookup_key_string(key: String) -> String { + if cfg!(windows) { + key.to_ascii_lowercase() + } else { + key + } + } pub fn get_config_profile( &self, override_profile: Option, diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 8d5c64a623..f37f4e152d 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -536,7 +536,9 @@ async fn load_requirements_from_legacy_scheme( struct ProjectTrustContext { project_root: AbsolutePathBuf, project_root_key: String, + project_root_lookup_keys: Vec, repo_root_key: Option, + repo_root_lookup_keys: Option>, projects_trust: std::collections::HashMap, user_config_file: AbsolutePathBuf, } @@ -559,28 +561,33 @@ impl ProjectTrustDecision { impl ProjectTrustContext { fn decision_for_dir(&self, dir: &AbsolutePathBuf) -> ProjectTrustDecision { - let dir_key = dir.as_path().to_string_lossy().to_string(); - if let Some(trust_level) = self.projects_trust.get(&dir_key).copied() { - return ProjectTrustDecision { - trust_level: Some(trust_level), - trust_key: dir_key, - }; + for dir_key in normalized_project_trust_keys(dir.as_path()) { + if let Some(trust_level) = self.projects_trust.get(&dir_key).copied() { + return ProjectTrustDecision { + trust_level: Some(trust_level), + trust_key: dir_key, + }; + } } - if let Some(trust_level) = self.projects_trust.get(&self.project_root_key).copied() { - return ProjectTrustDecision { - trust_level: Some(trust_level), - trust_key: self.project_root_key.clone(), - }; + for project_root_key in &self.project_root_lookup_keys { + if let Some(trust_level) = self.projects_trust.get(project_root_key).copied() { + return ProjectTrustDecision { + trust_level: Some(trust_level), + trust_key: project_root_key.clone(), + }; + } } - if let Some(repo_root_key) = self.repo_root_key.as_ref() - && let Some(trust_level) = self.projects_trust.get(repo_root_key).copied() - { - return ProjectTrustDecision { - trust_level: Some(trust_level), - trust_key: repo_root_key.clone(), - }; + if let Some(repo_root_lookup_keys) = self.repo_root_lookup_keys.as_ref() { + for repo_root_key in repo_root_lookup_keys { + if let Some(trust_level) = self.projects_trust.get(repo_root_key).copied() { + return ProjectTrustDecision { + trust_level: Some(trust_level), + trust_key: repo_root_key.clone(), + }; + } + } } ProjectTrustDecision { @@ -645,26 +652,84 @@ async fn project_trust_context( let project_root = find_project_root(cwd, project_root_markers).await?; let projects = project_trust_config.projects.unwrap_or_default(); - let project_root_key = project_root.as_path().to_string_lossy().to_string(); + let project_root_lookup_keys = normalized_project_trust_keys(project_root.as_path()); + let project_root_key = project_root_lookup_keys + .first() + .cloned() + .unwrap_or_else(|| normalized_project_trust_key(project_root.as_path())); let repo_root = resolve_root_git_project_for_trust(cwd.as_path()); - let repo_root_key = repo_root + let repo_root_lookup_keys = repo_root .as_ref() - .map(|root| root.to_string_lossy().to_string()); + .map(|root| normalized_project_trust_keys(root)); + let repo_root_key = repo_root_lookup_keys + .as_ref() + .and_then(|keys| keys.first().cloned()); let projects_trust = projects .into_iter() - .filter_map(|(key, project)| project.trust_level.map(|trust_level| (key, trust_level))) + .flat_map(|(key, project)| { + project + .trust_level + .into_iter() + .flat_map(move |trust_level| { + normalized_project_trust_key_strs(&key) + .into_iter() + .map(move |normalized_key| (normalized_key, trust_level)) + }) + }) .collect(); Ok(ProjectTrustContext { project_root, project_root_key, + project_root_lookup_keys, repo_root_key, + repo_root_lookup_keys, projects_trust, user_config_file: user_config_file.clone(), }) } +fn normalized_project_trust_key(path: &Path) -> String { + normalized_project_trust_keys(path) + .into_iter() + .next() + .unwrap_or_else(|| normalize_project_trust_lookup_key(path.to_string_lossy().to_string())) +} + +fn normalized_project_trust_keys(path: &Path) -> Vec { + let normalized_path = normalize_project_trust_lookup_key(path.to_string_lossy().to_string()); + let normalized_canonical_path = normalize_project_trust_lookup_key( + normalize_path(path) + .unwrap_or_else(|_| path.to_path_buf()) + .to_string_lossy() + .to_string(), + ); + if normalized_path == normalized_canonical_path { + vec![normalized_path] + } else { + vec![normalized_path, normalized_canonical_path] + } +} + +fn normalized_project_trust_key_strs(path: &str) -> Vec { + let path = Path::new(path); + if path.is_absolute() { + normalized_project_trust_keys(path) + } else { + vec![normalize_project_trust_lookup_key( + path.to_string_lossy().to_string(), + )] + } +} + +fn normalize_project_trust_lookup_key(key: String) -> String { + if cfg!(windows) { + key.to_ascii_lowercase() + } else { + key + } +} /// Takes a `toml::Value` parsed from a config.toml file and walks through it, /// resolving any `AbsolutePathBuf` fields against `base_dir`, returning a new /// `toml::Value` with the same shape but with paths resolved.