Skip to content

fix(subdirectory_hints): catch RuntimeError from Path.expanduser() - #29433

Closed
udatny wants to merge 1 commit into
NousResearch:mainfrom
udatny:fix/subdirectory-hints-runtimeerror
Closed

fix(subdirectory_hints): catch RuntimeError from Path.expanduser()#29433
udatny wants to merge 1 commit into
NousResearch:mainfrom
udatny:fix/subdirectory-hints-runtimeerror

Conversation

@udatny

@udatny udatny commented May 20, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes a silent agent failure where any tool-call command containing a literal ~ in a non-path context (e.g. ~500-700 agencies, ~45,000 CVEs, ~80/hr blended rate — common LLM output meaning "approximately") causes the entire tool invocation to surface as Error during OpenAI-compatible API call #N: Could not determine home directory. from inside the conversation loop's catch-all.

The actual root cause is in agent/subdirectory_hints.py: Path(token).expanduser() raises RuntimeError when the tilde-expansion can't resolve to a real user, and the existing except (OSError, ValueError): clauses do not catch it. The exception bubbles up through the tool dispatcher and gets misreported as an API call error, masking the bug and making it look model-specific.

Related Issue

Fixes

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/subdirectory_hints.py:138except (OSError, ValueError)except (OSError, ValueError, RuntimeError) (in _add_path_candidate, which calls Path(raw_path).expanduser()).
  • agent/subdirectory_hints.py:198except ValueErrorexcept (ValueError, RuntimeError) (around hint_path.relative_to(self.working_dir), which also touches the tilde-expansion path internally).
  • agent/subdirectory_hints.py:202 — same as 198 around hint_path.relative_to(Path.home()) (where Path.home() itself can raise RuntimeError if HOME is not resolvable in the calling context).
  • tests/agent/test_subdirectory_hints.py — three new tests in a TestSubdirectoryHintTrackerTildeRobustness class:
    • test_tilde_approximately_in_command_does_not_crash: simulates an LLM heredoc with ~500-700/~45,000-style tokens. Without the fix, raises RuntimeError. With the fix, returns silently.
    • test_tilde_with_unknown_user_does_not_crash: ~nonexistent_user/path (POSIX), same expectation.
    • test_valid_tilde_user_still_works: regression guard so legitimate ~/Documents paths still work.

How to Test

Repro on main (without the fix):

>>> from pathlib import Path
>>> Path("~500-700").expanduser()
RuntimeError: Could not determine home directory.

Inside Hermes (without the fix), the symptom is:

$ cat > out.md <<EOF
Size signal: ~500-700 agencies
EOF
  0.0s [error]
❌ Error during OpenAI-compatible API call #2: Could not determine home directory.

After applying the fix:

pytest tests/agent/test_subdirectory_hints.py -q
# All TestSubdirectoryHintTrackerTildeRobustness tests pass.

Affected models we've reproduced this on (it is not model-specific):

  • openai/gpt-5-mini via OpenRouter
  • openai/gpt-5.1-codex via OpenRouter (mid-pipeline, ~33 turns in, on a heredoc containing ~80/hr blended rate)
  • deepseek/deepseek-v4-flash via OpenRouter

Checklist

Code

  • I've read the Contributing Guide
  • Conventional Commits: fix(subdirectory_hints): catch RuntimeError from Path.expanduser()
  • No duplicate PR — searched issues/PRs for "Could not determine home directory" and "subdirectory_hints"
  • PR contains only this fix + its test
  • pytest tests/agent/test_subdirectory_hints.py -q passes
  • Added tests covering the bug + a regression test for the legitimate-tilde case
  • Tested on: Linux (Docker nousresearch/hermes-agent:latest), Python 3.13

Documentation & Housekeeping

  • N/A — internal fix, no user-facing config or docs change
  • N/A — no config key changes
  • N/A — no architecture change
  • No cross-platform impact: bug and fix are pure Python/pathlib
  • No tool description / schema changes

Why this is hard to notice without the fix

The surfaced error string is "Error during OpenAI-compatible API call #N: Could not determine home directory." which strongly suggests an API-side or environment-side problem. In reality it's a pathlib exception three frames deep in the hint walker, wrapped twice by the conversation loop's catch-all. Once you know to look at subdirectory_hints.py, the one-line nature of the fix is obvious. Hopefully this PR saves the next user the few hours we spent tracing it.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 20, 2026

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

LGTM — strong candidate for merge

Reproduced the symptom on main against openai/gpt-5.1-codex and minimax-cn/MiniMax-M3 (this session) — a terminal command with ~500-700 in a heredoc blew up the tool dispatcher with the misleading "Could not determine home directory" error string. The author's three-line fix in subdirectory_hints.py resolves it.

Strengths

  • Reproduces a real LLM output pattern (~ as "approximately"), not a contrived edge case
  • Three test cases cover: buggy case, edge case, regression guard
  • The companion fix to Path.home() (lines 198/202) is a nice catch — same exception class affects relative_to(Path.home())
  • Conventional Commits + clean checklist

Suggestions (non-blocking)

  1. Rebase neededmain advanced 5 days since this branch was cut; mergeable_state: blocked likely from required CI checks rather than conflicts, but a rebase onto current main will rule that out.

  2. Broader blast radius — while reading the diff I grep'd the repo for other Path(...).expanduser() call sites that don't catch RuntimeError. Three adjacent ones worth considering for a follow-up PR (would happily file any/all if you want to keep this one focused):

    • agent/tool_dispatch_helpers.py:158expanded = Path(raw_path).expanduser() in _get_path_target. This is on the hot path for every path-scoped tool call (read_file/write_file/edit_file/...). Same bug class, higher impact than subdirectory_hints because the path comes straight from function_args["path"].
    • agent/skill_commands.py:63identifier_path = Path(raw_identifier).expanduser() in slash-command resolver. raw_identifier is user-supplied.
    • hermes_cli/gateway.py:2095-2101Path.home() itself can raise RuntimeError, only ValueError is caught.
  3. Optional: a lint rule — would prevent future regressions. A simple flake8-no-runtime-warn plugin or a one-off AST check that flags bare Path(...).expanduser() not wrapped in try/except (..., RuntimeError).

Verdict

LGTM. The PR is correctly scoped, well-tested, and addresses a real production pain point that misleads users into chasing model/environment issues. Recommending merge once rebased and CI green.

@gtyler

gtyler commented Jun 20, 2026

Copy link
Copy Markdown

Confirming this is the right fix — reproduced on main: Path.expanduser() raises RuntimeError("Could not determine home directory.") for a ~user token whose user has no passwd entry (the ~user form does a passwd lookup, so $HOME being set doesn't help), and it escapes the except (OSError, ValueError) in _add_path_candidate, aborting the whole agent turn (~3×/24h on a long-running gateway here). Tracked in #43963.

Two things worth folding in before this lands:

  • Second call site, same bug: hermes_cli/kanban_db.py:_is_managed_scratch_path() (~line 3781) resolves HERMES_KANBAN_WORKSPACES_ROOT via expanduser() and also catches only OSError — it'll crash the same way. Worth fixing in the same PR (or via the safe_expanduser() sweep in feat(utils): add safe_expanduser() — crash-free path expansion for HOME-unset environments #41870).
  • Regression test: a test that exercises the real ~nonexistentuser passwd-lookup path through check_tool_call would lock this in.

Closing my duplicate #43970 in favor of this earlier PR.

arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…ied, build exit-0, 210 tests pass single-worktree); review-comment walk all 41 PRs — 1 genuine duplicate (NousResearch#50049 vs NousResearch#29433) ADDRESSED on-PR + operator-deferred-close, rest are triage cross-refs affirming non-redundant
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…ndle)

This PR previously bundled 100 files as a "cross-PR integration regression suite",
but 94 of those duplicated other open PRs — which made it the primary blocker when
combining the PR set onto a later release (it conflicted on every overlapping file).

**Slimmed to the 4 files genuinely unique to this PR:**
```
hermes_cli/auth.py                                  # copilot-opus-context auth path
hermes_cli/runtime_provider.py                      # runtime provider resolution
tests/agent/conftest.py                             # shared test fixtures
tests/agent/test_copilot_opus_context_fix_2026_06_04.py   # the regression test
```

The 94 duplicate files are owned by their topical feature PRs already (autopilot
NousResearch#49917, reasoning NousResearch#48024, copilot identity NousResearch#50064, etc.). The 2 remaining "unique"
files from the old bundle (`agent/subdirectory_hints.py` + its test) belong to the
RuntimeError-guard lineage and are covered by the superset NousResearch#29433.

Built on v0.17.0 (`2bd1977d8`); all 4 files compile; 0 private-provenance leaks.
Slimming removes this PR as a combinability blocker (combine-conflicts 2 → 1).
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…LE) + reproducible delta map

- GITHUB-MERGEABLE-AUDIT.md: GitHub mergeable=40/41 (only NousResearch#50111 manifest conflicts).
  6 PRs (NousResearch#50296/NousResearch#49644/NousResearch#50041/NousResearch#50073/NousResearch#50064/NousResearch#50033) genuinely conflicted on current
  origin/main (drifted past v0.17.0); each rebased (1-file complementary conflict),
  now MERGEABLE.
- DELTA-MAP-v017.md: reproducible per-file map (PR diffs vs v0.17.0, fresh tips):
  160 = 137 in-PR + 21 DISCARD + 2 upstream-NousResearch#29433 + 0 orphans, sum verified.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…elta=165, 0 orphans)

- diff_coverage_proof.sh + DIFF-COVERAGE-PROOF.txt: PROVEN delta(165) = in-PR(138)
  + 9 .bak + 12 .project-intel + 4 transcripts + 2 upstream-NousResearch#29433, 0 orphans.
  CORRECTION: delta is 165 (not 160); 4 transcripts/ eval-captures were unclassified
  orphans, now DISCARD. DISCARD total = 25.
- PINNED-SHAS-API.txt: GitHub-API head SHAs (50053/50111 fixes confirmed landed).
- EVIDENCE-BUNDLE.md: 41-vs-40 reconciliation + CI-gated status + 0-leak scan.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
Adds the "xai" -> "xAI" entry to _LABEL_OVERRIDES so the provider list shows the
proper casing. Scoped to this single label change; the subdirectory_hints.py
RuntimeError guard previously bundled here is deferred to the maintainer-preferred
open PR NousResearch#29433 (same fix), keeping this PR to one logical change.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…hint dup to upstream NousResearch#29433

NousResearch#50626 force-pushed to exactly hermes_cli/providers.py (+"xai":"xAI", 1 line) — its
subdirectory_hints.py RuntimeError guard duplicated maintainer-preferred open NousResearch#29433
(which ships the same fix + its own test), so it's deferred there (one-PR-per-change).
The 2 subdir-hint files move to a new SUPERSEDED-by-upstream coverage class. Coverage
re-balances: 165 = 129 in PRs + 25 DISCARD + 9 WITHDRAWN + 2 SUPERSEDED + 0 orphans.
Trimmed NousResearch#50626 verified applies-clean on v0.17.0. Maps reconciled.
@udatny
udatny force-pushed the fix/subdirectory-hints-runtimeerror branch from 974a5a1 to 2351002 Compare June 23, 2026 17:28
@udatny

udatny commented Jun 23, 2026

Copy link
Copy Markdown
Author

Rebased onto current main. The 3-line fix in subdirectory_hints.py plus the test file reapplied cleanly — no conflicts, same diff, just shifted line numbers (now 144 / 241 / 244 in current main). Head is 2351002, mergeable: MERGEABLE now.

Keeping this PR scoped to subdirectory_hints.py to keep review surface small. The other expanduser() sites you both flagged are real and have the same bug class — happy to file follow-up PRs for each:

  • @gtylerhermes_cli/kanban_db.py:_is_managed_scratch_path() (same except OSError only pattern). Your ~nonexistentuser real-passwd-lookup regression test is a stronger test than the synthesized one currently in the diff — would fold that into the follow-up.
  • @alexzhu0agent/tool_dispatch_helpers.py:_get_path_target (hot path for every path-scoped tool call — higher blast radius than this PR), plus agent/skill_commands.py and hermes_cli/gateway.py:Path.home().

Happy to consolidate into one omnibus PR instead if maintainers prefer — let me know.

@gtyler — thanks for closing #43970 in favor of this one.

`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.
@udatny
udatny force-pushed the fix/subdirectory-hints-runtimeerror branch from 2351002 to fef4124 Compare June 23, 2026 17:39
@udatny

udatny commented Jun 23, 2026

Copy link
Copy Markdown
Author

Small follow-up: the test file as originally submitted referenced a project fixture that's only defined locally in tests/agent/test_subdirectory_hints.py, so it didn't resolve when the file ran standalone (the README in my upstream-pr/ bundle said "append OR new file" but the new-file path was broken — my mistake).

Fixed by switching the three tests to pytest's built-in tmp_path — they don't need the richer mock-project tree anyway, just a working directory. Amended into the same commit (head now fef4124).

Verified on Linux:

PASS: test_tilde_approximately_in_command_does_not_crash
PASS: test_tilde_with_unknown_user_does_not_crash
PASS: test_valid_tilde_user_still_works

Also independently confirmed the bug-vs-fix behaviour on Path("~500-700").expanduser(): raises RuntimeError on POSIX without the patch, swallowed by the widened except with the patch. Windows doesn't reproduce (no passwd lookup on ~user), so the regression test is POSIX-meaningful only — but harmless on Windows.

waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused fix. This is already implemented on current main via the contributor-authored commit c126a99fc1e2f82a1e23ebe27fb52e26687fdafa and shipped in v2026.7.1.

Automated hermes-sweeper review evidence:

  • agent/subdirectory_hints.py:147 catches RuntimeError with the existing path-resolution errors; the corresponding display-path handlers are at lines 244 and 248.
  • tests/agent/test_subdirectory_hints_tilde.py:19 contains the approximate-tilde, unknown-user, and valid-tilde regression coverage.
  • git diff HEAD fef4124 -- agent/subdirectory_hints.py tests/agent/test_subdirectory_hints_tilde.py is empty, so the PR's changed paths are already identical on main.

@teknium1 teknium1 closed this Jul 13, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jul 13, 2026
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants