fix(context_references): @folder: listing works outside cwd under a widened allowed_root

@folder: targets resolve against allowed_root, which callers may widen
beyond cwd, but _build_folder_listing and _iter_visible_entries assumed
the resolved folder was under cwd: path.relative_to(cwd) raised
ValueError and the blanket except in _expand_reference surfaced it as a
confusing "not in the subpath of" warning instead of a listing.

The listing header now renders cwd-relative when possible, then
allowed_root-relative, else the absolute path. rg --files gets the
absolute folder path (rg echoes the arg as the output prefix, so the
lines parse correctly anywhere), the parent-dir walk drops only the
cwd stop-condition so in-cwd output is unchanged, and entry indentation
is computed relative to the target folder rather than cwd. The os.walk
fallback never assumed cwd.

Regression tests cover the widened-root target through the real
preprocess_context_references entry on both the rg and rg-blocked
paths, plus @file: parity and in-cwd display controls.
This commit is contained in:
beardthelion
2026-09-20 14:37:46 -05:00
committed by Teknium
parent 7d8e8f234f
commit 275002d7a4
2 changed files with 121 additions and 8 deletions

View File

@@ -294,7 +294,7 @@ def _expand_path_reference(ref: ContextReference, cwd: Path, *, allowed_root: Pa
if not (path.is_dir() if is_folder else path.is_file()):
return f"{ref.raw}: path is not a {ref.kind}", None
if is_folder:
listing = _build_folder_listing(path, cwd)
listing = _build_folder_listing(path, cwd, display_base=allowed_root)
return None, f"📁 {ref.raw} ({estimate_tokens_rough(listing)} tokens)\n{listing}"
if _is_binary_file(path):
# A bare "not supported" warning was a dead end (the model gave up); the file IS
@@ -511,12 +511,24 @@ def _is_binary_file(path: Path) -> bool:
return b"\x00" in fh.read(4096)
def _build_folder_listing(path: Path, cwd: Path, limit: int = 200) -> str:
lines = [f"{path.relative_to(cwd)}/"]
def _build_folder_listing(path: Path, cwd: Path, limit: int = 200, display_base: Path | None = None) -> str:
# The target may sit outside cwd when the caller widened allowed_root: show it relative to
# cwd when possible, else relative to the allowed root, else the absolute path.
shown: str | None = None
for base in (cwd, display_base):
if base is None:
continue
try:
shown = f"{path.relative_to(base)}/"
break
except ValueError:
continue
if shown is None:
shown = f"{path}/"
lines = [shown]
entries = _iter_visible_entries(path, cwd, limit=limit)
base_depth = len(path.relative_to(cwd).parts)
for entry in entries:
indent = " " * max(len(entry.relative_to(cwd).parts) - base_depth - 1, 0)
indent = " " * max(len(entry.relative_to(path).parts) - 1, 0)
lines.append(f"{indent}- {entry.name}/" if entry.is_dir() else f"{indent}- {entry.name} ({_file_metadata(entry)})")
if len(entries) >= limit:
lines.append("- ...")
@@ -526,16 +538,18 @@ def _build_folder_listing(path: Path, cwd: Path, limit: int = 200) -> str:
def _iter_visible_entries(path: Path, cwd: Path, limit: int) -> list[Path]:
"""Files under ``path`` via ``rg --files`` (honours ignore files), else an os.walk fallback."""
try:
rg = _run_quiet(["rg", "--files", str(path.relative_to(cwd))], cwd, 10)
# Absolute path arg: rg echoes it as the output prefix, so results stay correct even
# when the folder is outside cwd (a widened allowed_root target).
rg = _run_quiet(["rg", "--files", str(path)], cwd, 10)
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
rg = None
if rg is not None and rg.returncode == 0:
output: list[Path] = []
seen_dirs: set[Path] = set()
for line in [ln.strip() for ln in rg.stdout.splitlines() if ln.strip()][:limit]:
full = cwd / Path(line)
full = cwd / Path(line) # absolute lines pass through unchanged; defensive for relative
for parent in full.parents:
if parent == cwd or parent in seen_dirs or path not in {parent, *parent.parents}:
if parent in seen_dirs or path not in {parent, *parent.parents}:
continue
seen_dirs.add(parent)
output.append(parent)

View File

@@ -103,6 +103,105 @@ def test_folder_listing_falls_back_when_rg_is_blocked(sample_repo: Path):
assert not result.warnings
def test_folder_listing_outside_cwd_inside_widened_allowed_root(tmp_path: Path):
"""allowed_root may be widened beyond cwd; @folder: targets there must
still produce a listing rather than a ValueError-as-warning."""
from agent.context_references import preprocess_context_references
cwd = tmp_path / "proj"
cwd.mkdir()
shared = tmp_path / "shared"
(shared / "sub").mkdir(parents=True)
(shared / "a.txt").write_text("x\n", encoding="utf-8")
(shared / "sub" / "b.txt").write_text("y\n", encoding="utf-8")
result = preprocess_context_references(
"Review @folder:../shared",
cwd=cwd,
allowed_root=tmp_path,
context_length=100_000,
)
assert result.expanded
assert not result.warnings
assert "shared/" in result.message
assert "a.txt" in result.message
assert "b.txt" in result.message
def test_folder_listing_outside_cwd_rg_blocked_fallback(tmp_path: Path):
"""Same widened-root target with rg unavailable: the os.walk fallback
never assumed cwd either."""
from agent.context_references import preprocess_context_references
cwd = tmp_path / "proj"
cwd.mkdir()
shared = tmp_path / "shared"
(shared / "sub").mkdir(parents=True)
(shared / "a.txt").write_text("x\n", encoding="utf-8")
(shared / "sub" / "b.txt").write_text("y\n", encoding="utf-8")
real_run = subprocess.run
def blocked_rg(*args, **kwargs):
cmd = args[0] if args else kwargs.get("args")
if isinstance(cmd, list) and cmd and cmd[0] == "rg":
raise PermissionError("rg blocked by policy")
return real_run(*args, **kwargs)
with patch("agent.context_references.subprocess.run", side_effect=blocked_rg):
result = preprocess_context_references(
"Review @folder:../shared",
cwd=cwd,
allowed_root=tmp_path,
context_length=100_000,
)
assert result.expanded
assert not result.warnings
assert "shared/" in result.message
assert "b.txt" in result.message
def test_file_reference_outside_cwd_inside_widened_allowed_root(tmp_path: Path):
"""Control: @file: never assumed cwd, so it already worked under a
widened allowed_root; pin that parity with @folder:."""
from agent.context_references import preprocess_context_references
cwd = tmp_path / "proj"
cwd.mkdir()
shared = tmp_path / "shared"
shared.mkdir()
(shared / "a.txt").write_text("SHARED-CONTENT\n", encoding="utf-8")
result = preprocess_context_references(
"Read @file:../shared/a.txt",
cwd=cwd,
allowed_root=tmp_path,
context_length=100_000,
)
assert result.expanded
assert not result.warnings
assert "SHARED-CONTENT" in result.message
def test_folder_listing_inside_cwd_unchanged(sample_repo: Path):
"""Control: in-cwd listings keep their cwd-relative display shape."""
from agent.context_references import preprocess_context_references
result = preprocess_context_references(
"Review @folder:src/",
cwd=sample_repo,
context_length=100_000,
)
assert result.expanded
assert not result.warnings
assert "src/" in result.message
assert "main.py" in result.message