fix(tools): grep fallback follows a symlinked search root on the content lane

`_search_with_grep_pruned` built `find <root> -type f -exec grep …`; find's
`-type f` tests the link itself, so a symlinked root (file or directory)
handed grep nothing and search_files answered total_count 0 with no error
and no warning — byte-identical to "no match" on every platform. `find -H`
follows the operand and only the operand, so links met inside the tree keep
their traversal semantics, and the follow happens in the command on the
host that owns the link (SSH/container backends included). Same shape as
the files-lane fix in the previous commit.

The plain `grep -r` lane is unchanged: GNU grep already follows a
command-line symlink (measured); BSD grep skips it, which needs a
macOS-side follow-up.

Based on the analysis in #116271 by @liuhao1024, whose local
`os.path.islink` resolution would not see a link that exists only on a
remote execution host.

Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
This commit is contained in:
teknium1
2026-09-19 22:56:21 -07:00
committed by Teknium
parent 9308a168c3
commit d5ee184773
2 changed files with 32 additions and 1 deletions

View File

@@ -329,6 +329,34 @@ class TestSymlinkedRootOnTheFilesLane:
assert nothing.error is None and nothing.total_count == 0, (
f"an empty root ({root}) answered {nothing.total_count} file(s): {nothing.files!r}")
def test_symlinked_content_root_under_a_dot_dir_matches_on_the_grep_lane(self, tmp_path, monkeypatch):
"""The pruned grep lane (root under a dot-dir) must search through a symlinked
root instead of answering a silent zero (#116270).
``find <link> -type f`` returned no files on every platform, so the pipeline was
byte-identical to "no match"; ``find -H`` follows the operand.
"""
from tools.file_tools import _get_file_ops
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
hidden = tmp_path / ".dot"
(hidden / "real").mkdir(parents=True)
(hidden / "real" / "f.md").write_text("NEEDLE\n")
(hidden / "link.md").symlink_to(hidden / "real" / "f.md")
(hidden / "dirlink").symlink_to(hidden / "real")
ops = _get_file_ops(task_id="t-symlink-content-grep")
if not ops._has_command("grep"):
pytest.skip("grep not installed")
self._pin_engine(monkeypatch, ops, "grep")
for root in ("link.md", "dirlink"):
r = ops.search("NEEDLE", path=str(hidden / root), target="content")
assert r.error is None, r.error
assert r.total_count == 1, (
f"symlinked root {root} answered total_count={r.total_count} on the grep lane")
miss = ops.search("ABSENT_TOKEN", path=str(hidden / "dirlink"), target="content")
assert miss.error is None and miss.total_count == 0
def test_a_symlinked_root_pointing_at_home_is_still_refused(self, tmp_path, monkeypatch):
"""The no-rg breadth guard must classify the link's target (#116270)."""
import tools.file_operations as file_operations

View File

@@ -952,7 +952,10 @@ class SearchMixin:
grep's exit code, so a hard grep error surfaces as an empty result."""
grep_parts = self._grep_cmd(["grep", "-nHE"], pattern, output_mode, context)
q_root = self._escape_shell_arg(path or ".")
find_parts = ["find", q_root]
# ``-H``: follow a symlink handed in as the OPERAND (and only the operand). Without
# it ``find <link> -type f`` tests the link itself and hands grep nothing, so a
# symlinked root answered a confident ``total_count: 0`` on every platform (#116270).
find_parts = ["find", "-H", q_root]
if protected_paths:
find_parts.extend([self._prune_expr(protected_paths), "-o"])
find_parts.extend([self._hidden_prune_expr([q_root]), "-o", "-type f"])