Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions agent/coding_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]),
Expand Down
28 changes: 27 additions & 1 deletion tests/agent/test_coding_context.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -158,6 +161,29 @@ def test_context_files_listed(self, tmp_path):
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.
Expand Down
Loading