Compare commits

...

2 Commits

Author SHA1 Message Date
Brent Traut
0ea86a2d76 codex: address PR review feedback (#24540) 2026-05-25 23:48:17 -07:00
Brent Traut
820b234810 core: inherit AGENTS override in linked worktrees 2026-05-25 23:29:38 -07:00
2 changed files with 62 additions and 2 deletions

View File

@@ -25,6 +25,8 @@ use codex_exec_server::Environment;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::LOCAL_FS;
use codex_features::Feature;
use codex_git_utils::get_git_repo_root_with_fs;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize as normalize_path;
use std::io;
@@ -293,6 +295,15 @@ impl<'a> AgentsMdManager<'a> {
}
}
let linked_checkout_roots = match (
get_git_repo_root_with_fs(fs, &dir).await,
resolve_root_git_project_for_trust(fs, &dir).await,
) {
(Some(checkout_root), Some(repo_root)) if checkout_root != repo_root => {
Some((checkout_root, repo_root))
}
_ => None,
};
let search_dirs: Vec<AbsolutePathBuf> = if let Some(root) = project_root {
let mut dirs = Vec::new();
let mut cursor = dir.clone();
@@ -315,8 +326,20 @@ impl<'a> AgentsMdManager<'a> {
let mut found: Vec<AbsolutePathBuf> = Vec::new();
let candidate_filenames = self.candidate_filenames();
for d in search_dirs {
for name in &candidate_filenames {
let candidate = d.join(name);
let mut candidates = vec![(d.join(LOCAL_AGENTS_MD_FILENAME), false)];
if let Some((checkout_root, repo_root)) = linked_checkout_roots.as_ref()
&& let Ok(relative) = d.as_path().strip_prefix(checkout_root.as_path())
{
let inherited = repo_root.join(relative).join(LOCAL_AGENTS_MD_FILENAME);
candidates.push((inherited, true));
}
candidates.extend(
candidate_filenames
.iter()
.skip(1)
.map(|name| (d.join(name), false)),
);
for (candidate, is_inherited) in candidates {
match fs.get_metadata(&candidate, /*sandbox*/ None).await {
Ok(md) if md.is_file => {
found.push(candidate);
@@ -324,6 +347,7 @@ impl<'a> AgentsMdManager<'a> {
}
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(_) if is_inherited => continue,
Err(err) => return Err(err),
}
}

View File

@@ -7,6 +7,8 @@ use core_test_support::PathBufExt;
use core_test_support::TempDirExt;
use pretty_assertions::assert_eq;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;
@@ -394,6 +396,40 @@ async fn agents_local_md_preferred() {
);
}
#[tokio::test]
async fn linked_worktrees_inherit_agents_local_md_from_primary_checkout() {
let tmp = tempfile::tempdir().expect("tempdir");
let repo = tmp.path().join("repo");
let worktree = tmp.path().join("worktree");
let worktree_git_dir = repo.join(".git/worktrees/feature");
fs::create_dir_all(&worktree_git_dir).unwrap();
fs::create_dir_all(&worktree).unwrap();
fs::write(
worktree.join(".git"),
format!("gitdir: {}\n", worktree_git_dir.display()),
)
.unwrap();
fs::write(repo.join(LOCAL_AGENTS_MD_FILENAME), "primary local").unwrap();
fs::write(worktree.join(DEFAULT_AGENTS_MD_FILENAME), "versioned").unwrap();
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
cfg.cwd = worktree.abs();
assert_eq!(
get_user_instructions(&cfg).await.as_deref(),
Some("primary local")
);
#[cfg(unix)]
{
fs::set_permissions(&repo, fs::Permissions::from_mode(0o000)).unwrap();
let instructions = get_user_instructions(&cfg).await;
fs::set_permissions(&repo, fs::Permissions::from_mode(0o755)).unwrap();
assert_eq!(instructions.as_deref(), Some("versioned"));
}
}
/// When AGENTS.md is absent but a configured fallback exists, the fallback is used.
#[tokio::test]
async fn uses_configured_fallback_when_agents_missing() {