From df07cb15b9bb25e4f9fb8286a98c9e9670f9d957 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 2 Jun 2026 20:10:01 -0700 Subject: [PATCH] fix(app-server): avoid overwriting symlinked migration targets Co-authored-by: Codex noreply@openai.com --- .../src/config/external_agent_config.rs | 10 +++-- .../src/config/external_agent_config_tests.rs | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/codex-rs/app-server/src/config/external_agent_config.rs b/codex-rs/app-server/src/config/external_agent_config.rs index 18c276f07d..446706325c 100644 --- a/codex-rs/app-server/src/config/external_agent_config.rs +++ b/codex-rs/app-server/src/config/external_agent_config.rs @@ -1315,10 +1315,12 @@ fn count_missing_subdirectories(source: &Path, target: &Path) -> io::Result io::Result { - if !path.exists() { - return Ok(true); - } - if !path.is_file() { + let metadata = match fs::symlink_metadata(path) { + Ok(metadata) => metadata, + Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(true), + Err(err) => return Err(err), + }; + if !metadata.is_file() { return Ok(false); } diff --git a/codex-rs/app-server/src/config/external_agent_config_tests.rs b/codex-rs/app-server/src/config/external_agent_config_tests.rs index 47f93a5f8b..62a50c4d84 100644 --- a/codex-rs/app-server/src/config/external_agent_config_tests.rs +++ b/codex-rs/app-server/src/config/external_agent_config_tests.rs @@ -1085,6 +1085,50 @@ async fn import_repo_agents_md_overwrites_empty_targets() { ); } +#[cfg(unix)] +#[tokio::test] +async fn repo_agents_md_migration_skips_symlink_targets() { + let root = TempDir::new().expect("create tempdir"); + let repo_root = root.path().join("repo"); + let linked_target = root.path().join("linked-agents.md"); + fs::create_dir_all(repo_root.join(".git")).expect("create git"); + fs::write( + repo_root.join(EXTERNAL_AGENT_CONFIG_MD), + format!("{SOURCE_EXTERNAL_AGENT_DISPLAY_NAME} code guidance"), + ) + .expect("write source"); + fs::write(&linked_target, "").expect("write linked target"); + std::os::unix::fs::symlink(&linked_target, repo_root.join("AGENTS.md")) + .expect("create target symlink"); + + let service = service_for_paths( + root.path().join(EXTERNAL_AGENT_DIR), + root.path().join(".codex"), + ); + let items = service + .detect(ExternalAgentConfigDetectOptions { + include_home: false, + cwds: Some(vec![repo_root.clone()]), + }) + .await + .expect("detect"); + assert_eq!(items, Vec::::new()); + + service + .import(vec![ExternalAgentConfigMigrationItem { + item_type: ExternalAgentConfigMigrationItemType::AgentsMd, + description: String::new(), + cwd: Some(repo_root), + details: None, + }]) + .await + .expect("import"); + assert_eq!( + fs::read_to_string(linked_target).expect("read linked target"), + "" + ); +} + #[tokio::test] async fn detect_repo_prefers_non_empty_external_agent_agents_source() { let root = TempDir::new().expect("create tempdir");