Skip to content

fix(search): keep native Windows paths for rg/grep on Windows - #75455

Closed
wertigoxxx wants to merge 6 commits into
NousResearch:mainfrom
wertigoxxx:fix/search-files-windows-native-rg-path
Closed

fix(search): keep native Windows paths for rg/grep on Windows#75455
wertigoxxx wants to merge 6 commits into
NousResearch:mainfrom
wertigoxxx:fix/search-files-windows-native-rg-path

Conversation

@wertigoxxx

@wertigoxxx wertigoxxx commented Jul 31, 2026

Copy link
Copy Markdown

_escape_shell_arg rewrites C:... to /c/... for bash builtins, but native Windows rg (WinGet/MSVC build) cannot resolve /c/... when MSYS2_ARG_CONV_EXCL=* disables MSYS argument conversion - it treats the path as root-relative and fails with os error 3. _search_files_rg swallowed the error via 2>/dev/null, silently returning 0 results.

Add _escape_shell_arg_native() which quotes without the /c/ rewrite and converts MSYS paths back to native form; use it for the path argument in _search_files_rg, _search_with_rg and _search_with_grep.

@wertigoxxx
wertigoxxx force-pushed the fix/search-files-windows-native-rg-path branch from 2be93e9 to 9abeb0a Compare July 31, 2026 14:13

@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 the focused Windows search-path fix. The current implementation confirms the premise: tools/file_operations.py:961-976 rewrites native paths for Bash, while all affected search paths still use that helper (tools/file_operations.py:2221, 2232, 2288, 2418); Windows command environments disable MSYS conversion by default in tools/environments/local.py:1178-1198.

Problems

  • The new tests validate only _escape_shell_arg_native. They do not verify that _search_files_rg, _search_with_rg, and _search_with_grep actually emit the native path after this wiring change.

Suggested changes

  • Add mock-environment command assertions for the sorted and fallback rg --files paths plus the rg and grep content-search paths, under _IS_WINDOWS = True.

Automated hermes-sweeper review.

Comment thread tools/file_operations.py
@@ -2221,7 +2238,7 @@ def _search_files_rg(self, pattern: str, path: str, limit: int, offset: int) ->
# Try mtime-sorted first (rg 13+); fall back to unsorted if not supported.

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 add a search-path command-construction test as well as the helper tests. The new helper tests do not prove this rg --files call (or the sibling rg/grep call sites) actually retains the native drive path.

@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 labels Jul 31, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #72347, #67940, and #63458: these open PRs repair the same native-Windows search-path failure with materially different backend gating and call-site scope. Please consolidate the preferred coverage rather than treating this as a strict duplicate.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 31, 2026
@wertigoxxx
wertigoxxx force-pushed the fix/search-files-windows-native-rg-path branch from 0825211 to 54cfb92 Compare July 31, 2026 14:35
_escape_shell_arg rewrites C:\... to /c/... for bash builtins, but native
Windows rg (WinGet/MSVC build) cannot resolve /c/... when
MSYS2_ARG_CONV_EXCL=* disables MSYS argument conversion - it treats the
path as root-relative and fails with os error 3. _search_files_rg
swallowed the error via 2>/dev/null, silently returning 0 results.

Add _escape_shell_arg_native() which quotes without the /c/ rewrite and
converts MSYS paths back to native form; use it for the path argument in
_search_files_rg, _search_with_rg and _search_with_grep.
Regression tests for the native Windows path form: drive paths stay
intact, MSYS /c/... input converts back, relative paths unchanged, and
non-Windows hosts are a no-op (platform-independent via _IS_WINDOWS
monkeypatch).
Per review feedback: verify the actual commands emitted by
_search_files_rg (sorted + fallback), _search_with_rg and
_search_with_grep carry the native Windows drive path, not the /c/...
rewrite.
- LC_ALL=C for find so '%T@' mtime is locale-independent: on non-C
  locales (e.g. hu_HU) Cygwin find emits NBSP thousands separators
  (byte 0xa0) and a comma decimal separator, breaking UTF-8 decoding
  and the 'mtime path' parse.
- Normalize find output paths from MSYS form (/c/Users/x) to the
  native drive form so results match the rg path and resolve
  correctly in hidden-root filtering.
subprocess.run(shell=True) invokes cmd.exe on Windows, which cannot
run the find/sort/tail pipeline the hidden-path tests drive (and
shell=True + executable mangles args as '<exe> /c ...'). Run
["bash", "-c", command] explicitly instead. The umask permission
test asserts POSIX semantics that don't exist on Windows - skip it.
Consolidation per triage (related NousResearch#72347, NousResearch#67940):
- _uses_native_windows_search_paths() gates the MSYS->native path
  conversion to the LOCAL Git Bash backend on Windows; remote/container
  backends keep POSIX path semantics even when the host is Windows
- _check_lint now passes the {file} placeholder through the native path
  form: linters (node/go/rustfmt) are native Windows binaries that reject
  the MSYS /c/... spelling

Adds a lint command-construction regression test.
@wertigoxxx
wertigoxxx force-pushed the fix/search-files-windows-native-rg-path branch from 5bd7115 to 323d0a3 Compare July 31, 2026 21:09
@monerostar

Copy link
Copy Markdown
Contributor

Native Windows 11 verification (monerostar)

Could not leave a formal Approve (fork collaborator scope) — posting live evidence instead.

Host: native Windows 11 · Python 3.11.15 · pytest 9.1.1
rg: BurntSushi.ripgrep MSVC 15.1.0 via WinGet (not MSYS rg)
Checkout: refs/pull/75455/head

Root cause reproduced outside Hermes

With MSYS2_ARG_CONV_EXCL=* (same exclusion LocalEnvironment uses), MSVC rg.exe:

path form result
C:\Users\Admin\AppData\Local\Temp\hermes-rg-probe-… exit 0, hit found
/c/Users/Admin/AppData/Local/Temp/hermes-rg-probe-… exit 2, os error 3: The system cannot find the path specified

So rewriting the search root to /c/... is fatal for native WinGet/MSVC rg — matching the PR description.

PR helpers on live LocalEnvironment

uses_native: True
escape_native sample: 'C:\Users\Admin\...\hermes-rg-live-…'
escape_bash   sample: '/c/Users/Admin/.../hermes-rg-live-…'

End-to-end ShellFileOperations.search against a temp tree:

  • content search for a unique token → total_count: 1, native C:\...\hit.py path
  • files search *.pytotal_count: 1, same native path form

Tests

# PR-focused helpers / rg / grep / lint path emission
20 passed, 30 deselected

# Full tests/tools/test_file_operations.py
46 passed, 4 skipped

Notes

  • Gating native path form to local Windows + LocalEnvironment is the right boundary — remote/container backends should keep POSIX semantics even when the Hermes host is Windows.
  • Linter path change is consistent with the same native-binary class (node/go/etc.).

LGTM from a native Win11 machine with real MSVC rg.

@3750gustavo

Copy link
Copy Markdown

for anyone having similar issues I found you can make hermes read this git issue/request, and make a python script and temporary skill for using as substitute for search while this issue is not merged, just make sure to tell hermes to not edit its own files to avoid breaking hermes updater

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Twenty-five PRs reference this Windows path-handling complex; five directly address the outbound native-ripgrep failure, while the remainder cover distinct terminal-CWD, Bash-selection, inbound file-resolution, deleted-CWD, process-cleanup, or NUL-redirection causes. For the reported search failure, #67914 has the strongest diff because it converts native and MSYS drive forms to Bash-safe native C:/... arguments only for the local backend, whereas #75455 overlaps that repair but retains backslash paths and broadens scope to grep, lint, and find behavior.

Related pull requests

Duplicates

The terminal/CWD chains consolidate into #56384 (#35653, #46586, #47854, #47920, #49833, #50191, and #50596), inbound file-tool normalization consolidates into #61915 (#44735, #44765, #46887, #46995, and #50488), and deleted-CWD recovery consolidates into #62189 over #62405. The native-rg chain is #67914 over #63458, #69183, #72922, and the rg portion of #75455; #69293 is the weaker duplicate direction of the withdrawn #65363 NUL work.

Suggested consolidation

Author action: rebase #75455 onto main or split out its independently reviewable grep/lint work, close its rg portion and #63458 as duplicates of #67914, and keep #67914 open with the salvage path identified by its recorded best-fix verdict: local-only C:/... native-executable paths, remote-backend protection, all rg call sites, and regex-preservation tests. Keep #62189 and #49374 separate with their contributor-requested behavioral tests and focused salvage scopes; the merged #56384 and #61915 already remain the reference implementations for the distinct terminal and inbound-resolution causes.

Complex graph

flowchart TD
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I63177(["issue #63177 (open)"])
    I67629(["issue #67629 (open)"])
    I75007(["issue #75007 (open)"])
    I77036(["issue #77036 (open)"])
    subgraph Dup63458 ["PRs duplicating each other"]
        P63458["PR #63458 (open)"]
        P67914["PR #67914 (open)"]
        P69183["PR #69183 (closed)"]
        P72922["PR #72922 (closed)"]
        P75455["PR #75455 (open)"]
    end
    P75455 -->|fixes| I63177
    P75455 -->|fixes| I67629
    P75455 -->|fixes| I75007
    P75455 -->|fixes| I77036
    class I63177 open
    class I67629 open
    class I75007 open
    class I77036 open
    class P63458 open
    class P67914 open
    class P69183 closed
    class P72922 closed
    class P75455 open
    class P67914 best
    class P67914 best
    class P67914 best
    class P67914 best
    class P75455 target
    click I63177 "https://github.com/NousResearch/hermes-agent/issues/63177"
    click I67629 "https://github.com/NousResearch/hermes-agent/issues/67629"
    click I75007 "https://github.com/NousResearch/hermes-agent/issues/75007"
    click I77036 "https://github.com/NousResearch/hermes-agent/issues/77036"
    click P63458 "https://github.com/NousResearch/hermes-agent/pull/63458"
    click P67914 "https://github.com/NousResearch/hermes-agent/pull/67914"
    click P69183 "https://github.com/NousResearch/hermes-agent/pull/69183"
    click P72922 "https://github.com/NousResearch/hermes-agent/pull/72922"
    click P75455 "https://github.com/NousResearch/hermes-agent/pull/75455"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 25 pull requests and 10 issues in this complex. Each diff was read against this issue; Assessment working set: 180 kB of PR diffs, 71 kB of issue/PR text, 66 kB of discussion (90 comments), 78 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@wertigoxxx

Copy link
Copy Markdown
Author

Why the broader scope is the fix, not scope creep

Thanks for the triage write-up. I'd like to make the case for the boundary #75455 draws, since the wider surface is deliberate and every piece of it was hit by a real failure.

The bug class is "a native Windows binary receives an MSYS path", not "rg fails". _escape_shell_arg rewrites C:\.../c/... because that is the spelling bash builtins need — but the same rewritten path is handed to every native binary in the search chain: rg (primary + mtime-sorted fallback), the find fallback, grep content search, and _check_lint's node/go/rustfmt invocations. On a Git Bash backend with MSYS2_ARG_CONV_EXCL=* these are all native WinGet/MSVC binaries that resolve /c/... as root-relative and die with os error 3. Fixing only the rg call site leaves the siblings broken with the same silent failure — and the silence is the worst part, because _search_files_rg discards stderr via 2>/dev/null and reports "0 results" as if the search ran. Neither the agent nor the user can tell "no matches" from "path broken". The grep fallback is not adjacent scope: it is the safety net that runs precisely when rg cannot, so it is part of the same repair. (#77157, which cites this PR, reaches the same conclusion from the zero-match-probe side.)

We already have 25 PRs on this complex — because the fixes were narrow. Each one repaired a single call site (cwd normalization, cd conversion, inbound file resolution, outbound rg args…) and left the rest of the class broken for the next reporter, who then filed the next PR. #75455 closes the outbound half of the class in one diff: rg primary, rg sorted fallback, grep, find fallback, and the linter path — gated to the local backend only via _uses_native_windows_search_paths(), so remote/container backends keep POSIX semantics. That is the "fix the class, not the site" shape, and it should shrink the 25-PR pile rather than add a 26th.

On the backslash vs C:/ spelling: backslash retention is deliberate and verified — monerostar's native Win11 probe shows MSVC rg resolving C:\... cleanly (exit 0), and it round-trips with the native-form output the rest of the file tools already emit. Normalizing to C:/... buys nothing for CreateProcess-launched binaries and introduces a second spelling that has to be normalized again on every comparison. That said, if maintainers prefer the C:/ form, it is a one-line change in _escape_shell_arg_native — happy to align.

The broader scope also caught a second real bug. While covering the find fallback I hit the locale breakage: on non-C locales (e.g. hu_HU) Cygwin find emits NBSP thousands separators and a comma decimal separator in %T@ output, breaking UTF-8 decoding and the mtime/path parse on the fallback path (fixed with LC_ALL=C in ecd7da4). Same class as the original report — "works on en_US, silently breaks elsewhere" — and invisible to an rg-only PR.

Test coverage is on the wiring, not just the helper. Per the sweeper review, d1acb46 asserts the actual emitted commands for the sorted and fallback rg --files paths, both content-search paths, and the lint path under _IS_WINDOWS = True — plus monerostar's live verification on native Windows 11 with real MSVC rg (46 passed / 4 skipped).

If consolidation is the call, I'd suggest adopting #75455 as the base and folding #67914's C:/ normalization in as a tweak — but I'm equally happy to narrow this PR if maintainers prefer #67914 plus a follow-up for grep/find/lint.

@teknium1

Copy link
Copy Markdown
Contributor

Closing as implemented on main: #84378 (merged) covers this fix — a shared native-path escaper (_escape_native_tool_arg) applied to all six rg call sites, the zero-match probes, and the shell linter interpolation, with Windows regression tests. Thank you for diagnosing this — your PR correctly identified the same root cause (native binaries + MSYS_NO_PATHCONV means /c/... is never translated back), and the merged fix lands the same approach across the full call-site set. Sorry we couldn't land this one directly.

@teknium1 teknium1 closed this Aug 12, 2026
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-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.

6 participants