mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(subdirectory_hints): catch RuntimeError from Path.expanduser()
`pathlib.Path('~user').expanduser()` raises RuntimeError when the
tilde-expansion can't resolve the user (e.g. `~500-700` where the LLM
meant "approximately 500-700" rather than a path). The hint walker's
existing `except (OSError, ValueError):` clauses do not catch
RuntimeError, so it escapes through the tool dispatcher and surfaces
in the conversation loop as a misleading
Error during OpenAI-compatible API call #N:
Could not determine home directory.
Reproduced across three unrelated models (openai/gpt-5-mini,
openai/gpt-5.1-codex, deepseek/deepseek-v4-flash) on terminal-tool
commands containing literal tildes in non-path contexts — common in
LLM output ("~500 agencies", "~45,000 CVEs", "~80/hr blended rate").
Reproduction (one-liner):
>>> from pathlib import Path
>>> Path("~500-700").expanduser()
RuntimeError: Could not determine home directory.
Fix: extend the three `except` clauses in
agent/subdirectory_hints.py to also catch RuntimeError:
line 138 (_add_path_candidate's outer catch around the Path().expanduser() call)
lines 198+202 (_load_hints_for_directory's nested catches around hint_path.relative_to(Path.home()))
Tests: tests/agent/test_subdirectory_hints_tilde.py adds three cases
covering: tilde-as-approximately in heredoc commands, ~unknown_user paths,
and a regression guard that legitimate ~/path expansion still works.
This commit is contained in:
parent
18a9467fca
commit
c126a99fc1
2 changed files with 57 additions and 3 deletions
|
|
@ -144,7 +144,7 @@ class SubdirectoryHintTracker:
|
|||
if parent == p:
|
||||
break # filesystem root
|
||||
p = parent
|
||||
except (OSError, ValueError):
|
||||
except (OSError, ValueError, RuntimeError):
|
||||
pass
|
||||
|
||||
def _extract_paths_from_command(self, cmd: str, candidates: Set[Path]):
|
||||
|
|
@ -241,11 +241,11 @@ class SubdirectoryHintTracker:
|
|||
rel_path = str(hint_path)
|
||||
try:
|
||||
rel_path = str(hint_path.relative_to(self.working_dir))
|
||||
except ValueError:
|
||||
except (ValueError, RuntimeError):
|
||||
try:
|
||||
rel_path = str(hint_path.relative_to(Path.home()))
|
||||
rel_path = "~/" + rel_path
|
||||
except ValueError:
|
||||
except (ValueError, RuntimeError):
|
||||
pass # keep absolute
|
||||
found_hints.append((rel_path, content))
|
||||
# First match wins per directory (like startup loading)
|
||||
|
|
|
|||
54
tests/agent/test_subdirectory_hints_tilde.py
Normal file
54
tests/agent/test_subdirectory_hints_tilde.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""Regression tests for the home-directory RuntimeError bug.
|
||||
|
||||
Without the fix to ``agent/subdirectory_hints.py`` (add ``RuntimeError`` to
|
||||
the three ``except`` clauses around ``Path.expanduser()`` /
|
||||
``Path.home()``), the first two tests raise ``RuntimeError`` from inside
|
||||
the hint walker on POSIX systems.
|
||||
|
||||
These tests use pytest's built-in ``tmp_path`` fixture and intentionally
|
||||
do not depend on the richer ``project`` fixture from
|
||||
``test_subdirectory_hints.py`` so the file is runnable standalone.
|
||||
"""
|
||||
|
||||
from agent.subdirectory_hints import SubdirectoryHintTracker
|
||||
|
||||
|
||||
class TestSubdirectoryHintTrackerTildeRobustness:
|
||||
"""Regression: literal ``~`` in tool-call args must not crash the walker."""
|
||||
|
||||
def test_tilde_approximately_in_command_does_not_crash(self, tmp_path):
|
||||
"""LLMs use ``~`` for "approximately" (e.g. ``~500 agencies``).
|
||||
|
||||
``pathlib.Path('~500-700').expanduser()`` raises ``RuntimeError`` —
|
||||
the walker must catch this, not propagate it as a tool failure.
|
||||
"""
|
||||
tracker = SubdirectoryHintTracker(working_dir=str(tmp_path))
|
||||
# Heredoc-style terminal command body containing "~500-700"
|
||||
# used as "approximately 500-700"
|
||||
cmd = (
|
||||
"cat > out.md <<EOF\n"
|
||||
"Segment size signal: ~500-700 agencies in DACH region.\n"
|
||||
"CVE volume: ~45,000 disclosed in 2025.\n"
|
||||
"Founder blended rate: ~80/hr.\n"
|
||||
"EOF"
|
||||
)
|
||||
# Must not raise — return value can be None / empty
|
||||
tracker.check_tool_call("terminal", {"command": cmd})
|
||||
|
||||
def test_tilde_with_unknown_user_does_not_crash(self, tmp_path):
|
||||
"""``~unknown_user`` similarly raises RuntimeError on POSIX systems
|
||||
whose /etc/passwd does not contain that user. Walker must absorb it."""
|
||||
tracker = SubdirectoryHintTracker(working_dir=str(tmp_path))
|
||||
cmd = "echo path: ~nonexistent_user_xyzzy_12345/some/file"
|
||||
# Must not raise
|
||||
tracker.check_tool_call("terminal", {"command": cmd})
|
||||
|
||||
def test_valid_tilde_user_still_works(self, tmp_path):
|
||||
"""The fix must not regress the legitimate-tilde-user path.
|
||||
|
||||
``~`` alone resolves to ``Path.home()`` and should still be
|
||||
recognised as a candidate path (no exception either way).
|
||||
"""
|
||||
tracker = SubdirectoryHintTracker(working_dir=str(tmp_path))
|
||||
tracker.check_tool_call("terminal", {"command": "ls ~/Documents"})
|
||||
# No exception, no assertion required
|
||||
Loading…
Add table
Add a link
Reference in a new issue