From c126a99fc1e2f82a1e23ebe27fb52e26687fdafa Mon Sep 17 00:00:00 2001 From: ud Date: Wed, 20 May 2026 21:08:17 +0200 Subject: [PATCH] fix(subdirectory_hints): catch RuntimeError from Path.expanduser() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. --- agent/subdirectory_hints.py | 6 +-- tests/agent/test_subdirectory_hints_tilde.py | 54 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/agent/test_subdirectory_hints_tilde.py diff --git a/agent/subdirectory_hints.py b/agent/subdirectory_hints.py index 858807aba2d..ca96c664cb5 100644 --- a/agent/subdirectory_hints.py +++ b/agent/subdirectory_hints.py @@ -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) diff --git a/tests/agent/test_subdirectory_hints_tilde.py b/tests/agent/test_subdirectory_hints_tilde.py new file mode 100644 index 00000000000..5c6ad74f0e6 --- /dev/null +++ b/tests/agent/test_subdirectory_hints_tilde.py @@ -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 <