fix: tolerate windows trust path aliases

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-03-28 04:41:10 -07:00
parent 959e719f00
commit 2e23ca8650
2 changed files with 127 additions and 31 deletions

View File

@@ -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<ProjectConfig> {
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<String> {
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<String>,

View File

@@ -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<String>,
repo_root_key: Option<String>,
repo_root_lookup_keys: Option<Vec<String>>,
projects_trust: std::collections::HashMap<String, TrustLevel>,
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<String> {
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<String> {
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.