diff --git a/agent/coding_context.py b/agent/coding_context.py index 9e6f67e5249..79bc0c92e3c 100644 --- a/agent/coding_context.py +++ b/agent/coding_context.py @@ -711,10 +711,13 @@ def build_coding_workspace_block(cwd: Optional[str | Path] = None) -> str: lines.append("- Branch: (detached HEAD)") # Linked worktree: the per-worktree git dir differs from the shared common dir. + # We surface the fact that it's a worktree (so the model knows branches/stashes + # are shared state) but deliberately do NOT expose the primary tree path — + # giving the model a second absolute path causes it to sometimes run commands + # in the wrong directory. git_dir, common_dir = _git(root, "rev-parse", "--git-dir"), _git(root, "rev-parse", "--git-common-dir") if git_dir and common_dir and Path(git_dir).resolve() != Path(common_dir).resolve(): - main_tree = Path(common_dir).resolve().parent - lines.append(f"- Worktree: linked (primary tree at {main_tree})") + lines.append("- Worktree: linked (git state shared with primary tree)") dirty = [f"{n} {label}" for label, n in ( ("staged", counts["staged"]), ("modified", counts["modified"]), diff --git a/tests/agent/test_coding_context.py b/tests/agent/test_coding_context.py index 75749cb8fe9..b16b1737999 100644 --- a/tests/agent/test_coding_context.py +++ b/tests/agent/test_coding_context.py @@ -1,7 +1,9 @@ """Tests for agent.coding_context — RuntimeMode seam, resolver, toolset, git probe.""" import json +import os import subprocess +import shutil from pathlib import Path import pytest @@ -13,12 +15,13 @@ def _git_init(path): env = { "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t", + "HOME": str(path), } for args in ( ["init", "-q", "-b", "main"], ["commit", "-q", "--allow-empty", "-m", "init commit"], ): - subprocess.run(["git", "-C", str(path), *args], check=True, env={**env, "HOME": str(path)}) + subprocess.run([shutil.which("git"), "-C", str(path), *args], check=True, env=env) # ── resolver ────────────────────────────────────────────────────────────── @@ -158,6 +161,29 @@ class TestProjectFacts: block = cc.build_coding_workspace_block(tmp_path) assert "Context files: AGENTS.md" in block + def test_worktree_detected_without_primary_path(self, tmp_path): + # A linked worktree should be detected, but the output must NOT contain + # the absolute path to the primary tree — exposing that path causes the + # model to sometimes run commands in the wrong directory. + main_tree = tmp_path / "main" + main_tree.mkdir() + _git_init(main_tree) + worktree = tmp_path / "worktree" + subprocess.run( + ["git", "-C", str(main_tree), "worktree", "add", "-b", "wt-branch", str(worktree)], + check=True, + env={"PATH": os.environ.get("PATH", ""), "HOME": str(tmp_path), + "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", + "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t"}, + ) + block = cc.build_coding_workspace_block(worktree) + assert "Worktree: linked" in block + # The primary tree path must NOT appear anywhere in the output. + assert str(main_tree.resolve()) not in block + assert str(main_tree) not in block + # The worktree root IS the reported root. + assert f"Root: {worktree.resolve()}" in block or "Root:" in block + def test_marker_only_project_gets_snapshot_without_git(self, tmp_path): # A non-git project (manifest only) still gets a workspace snapshot — # just without the git lines.