Merge pull request #7224 from asto18089/up/worktree-discovery-anchor-only

fix(subagent): only scan nested repositories at the worktree anchor
This commit is contained in:
Hunter Bown authored and GitHub committed 2026-11-06 08:20:51 +08:00
commit 671f50977c
2 files changed
+76 -4

No files matched your search

+65 -3
View File
@@ -12793,9 +12793,9 @@ fn create_isolated_worktree_reports_friendly_error_when_no_repo_found() {
let harness = tempdir().expect("harness");
// Keep the probe more than `git_repo_root`'s parent-search limit below
// the temporary root. Containerized CI commonly checks the repository out
// at `/workspace`; a shallow `/tmp` fixture can otherwise reach `/` and
// correctly discover that sibling checkout instead of exercising the
// no-repository path.
// at `/workspace`; ancestors no longer adopt lone child checkouts, but
// keeping the walk inside the temp region means the test never depends on
// whatever other checkouts share its ancestry.
let no_repo = harness
.path()
.join("not-a-repo")
@@ -12847,6 +12847,68 @@ fn create_isolated_worktree_rejects_ambiguous_nested_repos() {
);
}
#[test]
fn create_isolated_worktree_ignores_unrelated_repos_above_the_workspace() {
// Incident shape: the workspace is not itself a repository and sits a few
// levels below a directory that holds unrelated checkouts. Scanning the
// children of every ancestor made the walk reach that directory and
// report the user's other projects as "multiple repositories" even
// though none of them contains the workspace.
let harness = tempdir().expect("harness");
for name in ["RepoA", "RepoB"] {
let sibling = harness.path().join(name);
std::fs::create_dir_all(&sibling).expect("sibling dir");
init_git_repo_at(&sibling);
}
let workspace = harness
.path()
.join("a")
.join("b")
.join("c")
.join("workspace");
std::fs::create_dir_all(&workspace).expect("workspace dir");
let worktree_home = tempdir().expect("worktree home");
let request = SubAgentWorktreeRequest {
branch: Some("codex/agent-ancestor-scan".to_string()),
path: Some(worktree_home.path().join("isolated")),
base_ref: None,
};
let err = create_isolated_worktree(&workspace, &request, None, &FleetRole::Worker)
.expect_err("no repository contains the workspace");
let message = err.to_string();
assert!(
!message.contains("Multiple git repositories"),
"ancestor directories must not adopt their children's repositories, got: {message}"
);
assert!(
message.contains("requires a git repository") && message.contains("Tried:"),
"expected friendly no-repository error, got: {message}"
);
// The walk must actually reach the ancestor holding the sibling
// repositories. A substring check cannot prove that: every tried path
// starts with the harness root, so it would match even if a shrunken
// parent-search limit stopped the walk short of the harness root and the
// test passed without ever scanning their children. Require the harness
// root as an exact entry of the "Tried:" list instead (mirrors
// git_repo_root_reports_attempted_paths_when_no_repo_found).
let harness_root = harness
.path()
.canonicalize()
.expect("canonical harness root")
.to_string_lossy()
.into_owned();
let tried = message
.split_once("Tried: ")
.expect("friendly error should list attempted paths")
.1;
assert!(
tried.split(", ").any(|path| path == harness_root),
"walk should reach the ancestor holding the sibling repos, got: {message}"
);
}
#[test]
fn build_subagent_system_prompt_appends_role_when_set() {
let assignment = SubAgentAssignment::new("p".to_string(), Some("worker".to_string()));
+11 -1
View File
@@ -197,7 +197,17 @@ pub(super) fn git_repo_root(workspace: &Path) -> Result<PathBuf, ToolError> {
return Ok(root);
}
if let Ok(entries) = fs::read_dir(dir) {
// Only the anchor itself may adopt a single nested checkout (a test
// harness or a session workspace that wraps one repository). Ancestors
// are probed for containment only; unlike base they no longer adopt a
// lone repository found among their children, because scanning them
// adopts or enumerates repositories the user never pointed at — a
// session workspace a few levels below the home directory walked up
// to `$HOME` and reported the user's other projects as "multiple
// repositories".
if levels == 0
&& let Ok(entries) = fs::read_dir(dir)
{
let mut nested_roots = Vec::new();
for entry in entries.flatten() {
let child = entry.path();