Skip to content

fix(search): zero-match probes fall back to grep; native paths for rg on Windows - #77157

Open
andrexibiza wants to merge 2 commits into
NousResearch:mainfrom
andrexibiza:fix/search-zero-match-probe-grep-fallback
Open

fix(search): zero-match probes fall back to grep; native paths for rg on Windows#77157
andrexibiza wants to merge 2 commits into
NousResearch:mainfrom
andrexibiza:fix/search-zero-match-probe-grep-fallback

Conversation

@andrexibiza

@andrexibiza andrexibiza commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

What changed and why

This PR fixes the native-Windows search_files path boundary while preserving the existing MSYS-safe path handling used by shell builtins and remote backends.

Hermes intentionally sets MSYS_NO_PATHCONV=1 / MSYS2_ARG_CONV_EXCL=* for local Git Bash commands. _escape_shell_arg() therefore correctly produces /c/... for Bash/MSYS consumers, but that same spelling reaches native Win32 rg.exe unchanged and cannot be resolved. The PR adds _escape_native_arg() and uses it only for the search-root argument at the native ripgrep boundary:

  • _zero_match_probe (case-insensitive, hidden, and fixed-string probes)
  • _search_files_rg (sorted and fallback commands)
  • _search_with_rg

The conversion is gated to LocalEnvironment on Windows. SSH, WSL, Docker, and other remote backends keep their own path namespaces unchanged. Grep and shell builtins retain the existing MSYS-safe escaping.

Why this matters to users

Before this change, a Windows user searching an absolute path containing spaces could get an empty result or an os error 3 even though read_file could read the same file. Relative paths appeared to work, which made the failure look inconsistent and could cause the agent to reason from a false empty directory. After this change, native backslash paths, drive-letter paths, MSYS drive paths, and case-varied paths resolve consistently for local Windows search without changing remote-backend behavior.

Verification

  • RED on clean origin/main 2446c8bb6: real Windows 10 native rg 15.1.0 harness. read_file succeeded for native backslash, C:/, /c/, and case-varied paths containing spaces; search_files returned 0 and content search returned native-rg os error 3 for each existing path.
  • GREEN at PR head 6d8c4223549c2f1fa5d6a5af3bcaf3536fe337ad: the same real Windows harness returned read_file success, one content match, and one file match for all four path forms. A nonexistent path with spaces remained a structured Path not found result.
  • Focused tests: scripts/run_tests.sh tests/tools/test_search_zero_match_and_multipath.py tests/tools/test_file_operations.py -k 'native or escape_native or local_windows_backend or remote_backend or search_with_rg_uses or rg_path' — 5 passed.
  • PR CI at this head: all required checks pass, including Python tests, lints, Windows-footgun lint, E2E, attribution, and supply-chain checks.
  • python scripts/check-windows-footguns.py and git diff --check pass on the verified source.

Fixes #63177
Related: #67629, #75455, #82400

@andrexibiza

Copy link
Copy Markdown
Contributor Author

Verification evidence + merge status (from the contributor's Windows dev box, Aug 2)

tests/tools/test_search_zero_match_and_multipath.py:

  • This branch (1c014c2): 16 passed, 0 failed (~24s).
  • Pristine base (1c39f1c): 8 failed, 3 passed.
  • Current main (a6defd4): 10 failed, 5 passed — every failure is rg: /c/Users/...: The system cannot find the path specified (os error 3) (MSYS /c/... path handed to native rg.exe under MSYS2_ARG_CONV_EXCL=*), including TestZeroMatchProbeEngineParity::test_hint_is_attached_on_each_search_engine[rg].

Ruff: clean on both changed files.

Note for maintainers: this branch currently shows CONFLICTING — upstream main (40 commits past the fork point 1c39f1c) independently reworked the same _search_content region (a used_rg flag instead of the early return), which textually overlaps this commit. Conflict is confined to tools/file_operations.py; the test file auto-merges cleanly. main's rework does not include the two other fixes here (_zero_match_probe falling back to grep, and _escape_native_arg for native rg.exe on Windows) — which is why main still fails 10/15 on this file while this branch passes 16/16. A rebase onto current main with conflict resolution is needed for mergeability.

@alt-glitch alt-glitch added type/bug Something isn't working tool/file File tools (read, write, patch, search) platform/windows Native Windows-specific behavior or breakage P2 Medium — degraded but workaround exists needs-decision Awaiting maintainer decision before any implementation sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 2, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #77128 already landed the rg zero-match return-path repair. This PR additionally covers grep fallback probes and overlaps the open native-Windows rg-path fixes in #63458 and #75455; please consolidate the path-conversion approach when rebasing.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for carrying the grep-fallback and native-rg fixes. The premise remains partly valid on current main: _zero_match_probe() still returns immediately without rg at tools/file_operations.py:2278, and rg roots still use _escape_shell_arg() at tools/file_operations.py:2447, 2458, and 2542. The rg early-return portion is already implemented by 794d6c434e.

Problems

  • Proposed _escape_native_arg is host-OS gated, but command execution is backend-agnostic through self.env.execute(...) (tools/file_operations.py:876-881). This can rewrite a valid remote /mnt/d/... path on a Windows host. The exact SSH/WSL reproduction and a LocalEnvironment-only guard are documented in #67914.

Suggested changes

  • Limit native-rg conversion to the local Windows backend and add local-versus-remote path regression coverage.
  • Add one end-to-end forced-grep search() test that verifies a real zero-match hint is attached.

Automated hermes-sweeper review.

Comment thread tools/file_operations.py
— POSIX paths are identical either way.
"""
from tools.environments.local import _msys_to_windows_path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please scope this conversion to the local Windows backend, not host os.name alone. _exec() forwards commands to arbitrary self.env backends; a Windows-host SSH/container search can legitimately use /mnt/d/..., which this helper would turn into an invalid D:\... remote path. #67914 documents this exact case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 6d8c422354 — the native-path rewrite is now scoped to the executed backend, not the host OS: _escape_native_arg gates on _local_windows_backend() (tools/file_operations.py:1004/1028), so remote /mnt/d paths never get mangled. Verified at head 6d8c422354: 24/24 tests in tests/tools/test_search_zero_match_and_multipath.py pass.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Aug 3, 2026
… on Windows

The zero-match steering feature (5797b50/e7aa06c) was dead on the rg
path: _search_content returned early after the rg search, so the probe
block was unreachable whenever ripgrep was used — the 3 hint tests in
test_search_zero_match_and_multipath.py failed on any env with rg.

Also:
- _zero_match_probe hard-required rg and silently returned None when
  the main search had fallen back to grep (rg not on PATH). It now
  picks the same engine as the main search (rg -> grep) with per-engine
  count flags, so hints appear in grep-only environments too.
- Native rg.exe cannot read the MSYS /c/... path form when MSYS
  argument conversion is disabled (MSYS2_ARG_CONV_EXCL=*, set by the
  local env to stop flag mangling). New _escape_native_arg passes the
  C:/... form to rg in the content search, files search, and probes,
  fixing search on Windows. No-op off Windows.

New TestZeroMatchProbeGrepFallback covers the grep-engine hints
(case/literal/hidden/zero). Full file: 16 passed, 0 failed locally
(was 8 failed on the rg path before this change).
@andrexibiza
andrexibiza force-pushed the fix/search-zero-match-probe-grep-fallback branch from 1c014c2 to 2013a58 Compare August 3, 2026 00:05
@andrexibiza

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (a6defd4) — conflict in tools/file_operations.py resolved. Main's rework already ran the zero-match probe for both engines (used_rg flag); this branch adds what main still lacks: grep fallback inside the probe itself (main still hard-requires rg there) and Windows native-path handling for rg.exe (main still passes MSYS /c/ paths, which native rg cannot read). Re-verified on rebased commit 2013a58: tests/tools/test_search_zero_match_and_multipath.py = 20 passed / 0 failed, ruff clean.

Addresses teknium1's hermes-sweeper review on NousResearch#77157 and closes the
canonical Windows search bug (NousResearch#63177, dup NousResearch#77036).

The native-rg path rewrite (_escape_native_arg) was previously gated on
the host OS alone (os.name == 'nt'), but command execution is
backend-agnostic through self.env.execute: a Windows host driving a
remote SSH/WSL/Docker backend would rewrite valid remote paths such as
/mnt/d/... into an invalid D:\... form. The conversion now only applies
when the EXECUTED backend is the local Windows environment
(_local_windows_backend: isinstance(self.env, LocalEnvironment) and
_IS_WINDOWS, mirroring the _lsp_local_only gate). Remote paths pass
through unchanged.

The grep-fallback probe work (engine picker + per-engine flags) is kept
— main's _zero_match_probe still hard-requires rg. Upstream NousResearch#77128
already landed the rg early-return repair; main's structure is preserved
(probe runs on both engines, used_rg guard for the newline warning).

New tests: TestNativePathBackendGating (local /c/ -> C: conversion,
remote /mnt/d/ /mnt/c/ /home/ pass through even with _IS_WINDOWS=True,
remote probe command keeps the raw remote path) and
test_end_to_end_search_real_hint_via_forced_grep (real search() pipeline
with grep forced via _has_command, real probe, asserts the case-
insensitive hint in the result warning).

24 passed / 0 failed on the target file; broader-suite failure set
byte-identical to pristine origin/main (stash + worktree proven); ruff
clean. Independent QA critique: SAFE TO SHIP.
@andrexibiza

Copy link
Copy Markdown
Contributor Author

Addressed in 6d8c422 — the native-path rewrite is now scoped to the executed backend, not the host OS: _escape_native_arg gates on _local_windows_backend() (isinstance(self.env, LocalEnvironment) and _IS_WINDOWS, mirroring _lsp_local_only), so a Windows host driving a remote SSH/WSL/Docker backend leaves /mnt/d/... paths unchanged. Also closes the canonical #63177 (dup #77036). Added the two requested tests: TestNativePathBackendGating (local /c/C: conversion; remote /mnt/d/, /mnt/c/, /home/ pass through even with _IS_WINDOWS=True; remote probe command keeps the raw remote path) and test_end_to_end_search_real_hint_via_forced_grep (real search() with grep forced via _has_command, real probe, asserts the case-insensitive hint in the result warning). 24 passed / 0 failed on the target file; broader-suite failure set byte-identical to pristine origin/main; ruff clean.

@andrexibiza

Copy link
Copy Markdown
Contributor Author

Review receipt — comment from 2026-08-02 on tools/file_operations.py (_escape_shell_arg_native / zero-match path conversion):

Addressed in 6d8c422 — the native-path rewrite is now scoped to the LOCAL Windows backend (), not host os.name alone. A Windows host with a remote SSH/container backend keeps POSIX path semantics (/mnt/d/..., /c/... are real paths there — the exact #67914 case); only the local Git Bash env converts /c/Users/x -> C:\Users\x for native rg. Also closes the canonical bug #63177 (dup #77036).

Regression coverage added per your ask:

  • test_local_windows_backend_converts_msys_path
  • test_remote_backend_keeps_remote_path_unchanged
  • test_remote_backend_probe_command_keeps_remote_path

24/24 test_search_zero_match_and_multipath.py tests pass at head 6d8c422.

@andrexibiza

Copy link
Copy Markdown
Contributor Author

The exact gate (from tools/file_operations.py:1004): _IS_WINDOWS and isinstance(self.env, LocalEnvironment) — evaluated inside _uses_native_windows_search_paths(), imported from tools.environments.local at call time. Verification at head 6d8c422: 24/24 tests pass.

Copy link
Copy Markdown
Contributor Author

Decision recorded against the current search architecture.

Do not rebase this combined branch as the canonical landing vehicle. Both of its behavioral halves now have narrower owners:

The current #77157 head correctly absorbed the original review finding by gating conversion on the executed backend rather than host os.name, but rebasing it now would duplicate #84557 and #79605 and recreate a second helper/ownership path beside main's _escape_native_tool_arg.

Disposition:

  1. retain fix(search): zero-match probes fall back to grep; native paths for rg on Windows #77157 as provenance/interlock evidence for the combined failure class and its real-Windows red/green witness;
  2. carry any stronger tests not already present into fix: preserve POSIX rg paths for remote backends on Windows #84557 and fix(search): zero-match steering probe works on the grep fallback #79605, preserving this PR/@andrexibiza credit;
  3. land fix: preserve POSIX rg paths for remote backends on Windows #84557 for local-vs-remote path ownership and fix(search): zero-match steering probe works on the grep fallback #79605 for grep-probe parity;
  4. after both exact heads are green and the unique test evidence is preserved, record supersession and close fix(search): zero-match probes fall back to grep; native paths for rg on Windows #77157 rather than rebasing its production diff.

Required convergence tests across the two canonical owners: native C:\, C:/, and /c/ paths with spaces; remote /c/... and /mnt/d/... preservation from a Windows controller; rg and grep zero-match case/literal/hidden probes; explicit hidden roots; and structured hard-error versus legitimate-zero behavior.

The decision is complete. The current branch should not be landed independently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

search_files silently returns 0 results on Windows when passed an absolute path (rg + MSYS_NO_PATHCONV conflict)

3 participants