Skip to content

fix: allow search_files to work inside hidden directory paths - #18645

Closed
Kailigithub wants to merge 1 commit into
NousResearch:mainfrom
Kailigithub:fix/issue-18473-search-hidden-dirs
Closed

fix: allow search_files to work inside hidden directory paths#18645
Kailigithub wants to merge 1 commit into
NousResearch:mainfrom
Kailigithub:fix/issue-18473-search-hidden-dirs

Conversation

@Kailigithub

Copy link
Copy Markdown
Contributor

When the path argument contains hidden directories (e.g. ~/.hermes/skills/), search_files returns 0 results in both target='files' and target='content' modes (when ripgrep is not available).

Root cause:

  • _search_with_grep() uses --exclude-dir='.*' which matches any directory component, including the search root itself when it's inside a hidden directory
  • _search_files() find fallback uses -not -path '*/.*' with the same issue

Fix: Conditionally apply the hidden-directory exclusion only when the search path itself does not contain hidden directory components. This mirrors how ripgrep behaves natively — it excludes hidden dirs by default but still allows explicit hidden-directory search paths.

Changes:

  • _search_with_grep(): Skip --exclude-dir when the path has hidden components
  • _search_files() find fallback: Skip -not -path '*/.*' when the path has hidden components

Verification:

  • py_compile passes
  • All 50 existing test_file_operations tests pass

Closes #18473

@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #18487 and #16672 — same root cause (--exclude-dir/find filter self-excludes hidden search root). This PR combines both grep and find fixes. Closes #18473.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tools Tool registry, model_tools, toolsets tool/file File tools (read, write, patch, search) labels May 2, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #18487 and #16672

@liuhao1024 liuhao1024 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.

The fix correctly handles paths like ~/.hermes/skills/ where a hidden directory is part of the search root. However, there's an edge case with . and .. paths.

os.path.normpath('.') returns '.', and '.'.startswith('.') is True. Same for ... This means:

  • Searching in . (current directory) disables hidden directory exclusions — .git/, .cache/, etc. would be searched
  • Searching in .. or ../sibling also disables hidden exclusions

These are not hidden directories — they're special POSIX directory entries. The check should exclude them:

_path_parts = os.path.normpath(path).split(os.sep)
_has_hidden_component = any(
    p.startswith('.') and p not in ('.', '..')
    for p in _path_parts if p
)

The same fix should be applied in both _search_files and _search_with_grep.

This matters because the agent commonly passes relative paths like . when the user asks to search the current directory. With the current fix, those searches would silently include .git/ and other hidden directories, returning unexpected results.

@Kailigithub
Kailigithub force-pushed the fix/issue-18473-search-hidden-dirs branch from 8fc7886 to 3a0442b Compare May 3, 2026 03:19
luxles added a commit to luxles/hermes-agent that referenced this pull request Jun 19, 2026
… search roots

Two bugs in _search_with_grep() when ripgrep is unavailable:

1. BRE mode: grep -rnH uses Basic Regular Expression mode, where | is a
   literal character. ripgrep ships with Rust regex (| = alternation), so
   patterns with alternation silently fail on the grep fallback.
   Fix: add -E to enable ERE (Extended Regular Expression) mode.

2. --exclude-dir='.*': GNU grep applies this to all directory components
   INCLUDING command-line arguments, so a search under ~/.hermes/workspace
   skips the entire tree because .hermes matches .*. Ripgrep only applies
   globs to subdirectories, not the root.
   Fix: only add --exclude-dir when no path component is hidden, mirroring
   the logic already used by _search_files (merged in NousResearch#16672).

Both bugs produce silent false-negatives: search_files returns
total_count=0 without any error. Cross-reference NousResearch#18473, NousResearch#18645, NousResearch#34017.

Fixes NousResearch#18473

@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 isolating the hidden-root fallback issue. The grep half remains relevant on current origin/main: _search_content selects _search_with_grep when rg is unavailable (tools/file_operations.py:2244-2250), and that method still unconditionally adds --exclude-dir='.*' (tools/file_operations.py:2392-2394).

Problems

  • The target='files' / find half is already on main through merged PR #19878 (tools/file_operations.py:2118-2186; commit 4a1505a54796ac2a7396dcd9c406423291c450b9).
  • Skipping --exclude-dir for a hidden root also permits every hidden descendant below that root. The existing security regression identifies .hub/index-cache as unvetted adversarial content and requires grep to exclude it (tests/tools/test_search_hidden_dirs.py:3-13, :77-85).

Suggested changes

  • Keep the explicit-root bypass for grep, but filter only hidden descendants relative to that root, and add a forced-grep regression covering both a visible file and .hub/index-cache.

Automated hermes-sweeper review.

Comment thread tools/file_operations.py
cmd_parts.append("--exclude-dir='.*'")
# BUT: skip exclusion when the search path itself is inside a hidden
# directory, because --exclude-dir='.*' would exclude the search root.
_path_parts = os.path.normpath(path).split(os.sep)

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.

When the supplied root has a hidden ancestor, omitting this filter also exposes every hidden descendant below that root, including .hub/index-cache. Preserve the explicit-root bypass, but filter descendant path components relative to the requested root before returning results; tests/tools/test_search_hidden_dirs.py documents why that exclusion is security-sensitive.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 12, 2026
@Kailigithub

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #19878, which merged the hidden-root file-search fix into main on May 4. The merged change covers both fallback paths and includes regression tests; #18473 is now marked resolved by that PR. The original diff remains preserved at 3a0442b.

@Kailigithub

Copy link
Copy Markdown
Contributor Author

Reopening after direct source verification: #19878 fixed the fallback, but current main still unconditionally adds in . That leaves #18473's hidden-root bug unresolved. The previous supersede close was incorrect.

@Kailigithub Kailigithub reopened this Jul 20, 2026
@Kailigithub

Copy link
Copy Markdown
Contributor Author

Reopening after direct source verification: #19878 fixed the target='files' fallback, but current main still unconditionally adds --exclude-dir='.*' in _search_with_grep(). That leaves #18473's target='content' hidden-root bug unresolved. The previous supersede close was incorrect.

@Kailigithub

Copy link
Copy Markdown
Contributor Author

Closing as superseded — the same fix landed upstream via the salvage track in commit 3a0442b78e. No further action needed on this branch. 🤖

@Kailigithub

Copy link
Copy Markdown
Contributor Author

Closing per supersede comment above. The fix landed via salvage track.

@Kailigithub Kailigithub closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data 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.

[Bug]: search_files target='content' returns 0 results when search path contains hidden directories (e.g. ~/.hermes/)

4 participants