Agent context-file discovery loading Hermes install-tree AGENTS.md
Cluster 47753 · agent. Data as of
2 open items. Fix merged in #65485 on 16 Jul 2026. Check it covers your case.
The short answer
- Known?
- Yes. 7 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #65485. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #65485. That is separate from the fix event. - Still open
- 1 issue and 1 PR.
Next step: read #65485, then check whether your report is the one still open.
Read #65485Pick up a Lab taskMost members concern context-file discovery in the agent picking up the Hermes install tree's contributor AGENTS.md as project context, via bug reports and several fix PRs (including a salvage). One member is more tangential, about showing the full file path in a context file truncation warning.
Open issues1
- Issue#47753[Bug]: Hermes CLI launcher loads hermes-agent/AGENTS.md as project context for external workspace tasks
Open PRs1
- PR#47798fix(agent): show full file path in context file truncation warning
Fixes on record
-
#65485 fix(agent): never load the install-tree AGENTS.md as project context (salvage #64611)
files:
agent/prompt_builder.py,agent/runtime_cwd.py,agent/system_prompt.py,scripts/release.py,tests/agent/test_prompt_builder.py,tests/agent/test_runtime_cwd.py,tests/agent/test_system_prompt.pyShow the diff
-
agent/prompt_builder.py@@ -1957,6 +1957,7 @@ def build_context_files_prompt( cwd: Optional[str] = None, skip_soul: bool = False, context_length: Optional[int] = None, + allow_install_tree_fallback: bool = False, ) -> str: """Discover and load context files for the system prompt. @@ -1978,17 +1979,43 @@ def build_context_files_prompt( """ if cwd is None: cwd = os.getcwd() + cwd_is_fallback = True + else: + cwd_is_fallback = False cwd_path = Path(cwd).resolve() sections = [] - # Priority-based project context: first match wins - project_context = ( - _load_hermes_md(cwd_path, context_length) - or _load_agents_md(cwd_path, context_length) - or _load_claude_md(cwd_path, context_length) - or _load_cursorrules(cwd_path, context_length) - ) + # Never let a FALLBACK-picked directory inside the Hermes install/source + # tree gain system-prompt authority. A backend that self-spawns into that + # tree (the desktop app default) would otherwise load this repo's + # contributor AGENTS.md as authoritative project context (#64590). An + # explicitly configured cwd is honored verbatim — the Hermes tree is a + # legitimate workspace when the user deliberately points a session at it — + # and CLI-style surfaces pass allow_install_tree_fallback=True because + # their launch dir IS the user's shell cwd (developing Hermes in-tree). + from agent.runtime_cwd import _is_install_tree + + if ( + cwd_is_fallback + and not allow_install_tree_fallback + and _is_install_tree(cwd_path) + ): + logger.warning( + "skipping project-context discovery: working-directory resolution " + "fell back to the Hermes install tree (%s) — set terminal.cwd to " + "your project directory", + cwd_path, + ) + project_context = "" + else: + # Priority-based project context: first match wins + project_context = ( + _load_hermes_md(cwd_path, context_length) + or _load_agents_md(cwd_path, context_length) + or _load_claude_md(cwd_path, context_length) + or _load_cursorrules(cwd_path, context_length) + ) if project_context: sections.append(project_context) -
agent/runtime_cwd.py@@ -10,15 +10,36 @@ contextvar; CLI/cron fall through to `TERMINAL_CWD`/launch cwd. """ +import logging import os from contextvars import ContextVar, Token from pathlib import Path from typing import Any +logger = logging.getLogger(__name__) + _UNSET: Any = object() _SESSION_CWD: ContextVar = ContextVar("HERMES_SESSION_CWD", default=_UNSET) +# The Python package/source root (this file lives at <root>/agent/runtime_cwd.py). +# When a backend is launched from, or self-spawns into, this tree (the desktop +# app default), an os.getcwd() fallback would inject this repo's contributor +# AGENTS.md as authoritative project context. Context discovery must never +# resolve here. +_PACKAGE_ROOT = Path(__file__).resolve().parent.parent + + +def _is_install_tree(p: Path) -> bool: + # True only when p IS the package root or sits inside it. Ancestors of the + # package root (a user home that happens to contain the checkout, a --user + # site-packages parent) are legitimate workspaces and must not be blocked. + try: + p = p.resolve() + except Exception: + return False + return p == _PACKAGE_ROOT or _PACKAGE_ROOT in p.parents + def set_session_cwd(cwd: str | None) -> Token: """Pin the logical cwd for the current context.""" @@ -42,21 +63,38 @@ def resolve_agent_cwd() -> Path: p = Path(override).expanduser() if p.is_dir(): return p + logger.warning("configured working directory does not exist: %s", override) raw = os.environ.get("TERMINAL_CWD", "").strip() if raw: p = Path(raw).expanduser() if p.is_dir(): return p + logger.warning("TERMINAL_CWD does not exist: %s", raw) return Path(os.getcwd()) def resolve_context_cwd() -> Path | None: # None means "no configured cwd": build_context_files_prompt then falls back - # to the launch dir (os.getcwd()) — correct for the local CLI. The gateway - # avoids slurping its install dir by setting TERMINAL_CWD (see system_prompt.py) - # or, per session, the _SESSION_CWD contextvar above. + # to the launch dir (os.getcwd()), correct for a local CLI launched inside a + # real project. A configured path is validated here (previously it was passed + # through unchecked, diverging from resolve_agent_cwd). An explicitly + # configured path is otherwise honored verbatim — including the Hermes + # source tree itself, which is a legitimate workspace when the user is + # developing Hermes (per-surface policy for fallback-picked directories + # lives in build_context_files_prompt; see #64590). override = _session_cwd_override() if override: - return Path(override).expanduser() + p = Path(override).expanduser() + if not p.is_dir(): + logger.warning("configured working directory does not exist: %s", override) + else: + return p + return None raw = os.environ.get("TERMINAL_CWD", "").strip() - return Path(raw).expanduser() if raw else None + if raw: + p = Path(raw).expanduser() + if not p.is_dir(): + logger.warning("TERMINAL_CWD does not exist: %s", raw) + else: + return p + return None -
agent/system_prompt.py@@ -463,9 +463,16 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) # CLI), None lets build_context_files_prompt fall back to the launch # dir — the user's real cwd there, but the install dir for the gateway # daemon, which is why the gateway sets TERMINAL_CWD. + # + # allow_install_tree_fallback: for cli/tui the launch dir IS the + # user's shell cwd, so an in-tree fallback is a deliberate choice + # (developing Hermes). Every other surface (desktop chat panel, + # gateway daemons) self-spawns into the install tree, where the + # fallback would inject this repo's contributor AGENTS.md (#64590). context_files_prompt = _r.build_context_files_prompt( cwd=resolve_context_cwd(), skip_soul=_soul_loaded, - context_length=_ctx_len) + context_length=_ctx_len, + allow_install_tree_fallback=agent.platform in ("cli", "tui")) if context_files_prompt: context_parts.append(context_files_prompt) -
scripts/release.py@@ -46,6 +46,7 @@ # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { "[redacted:email]": "SAMBAS123", # PR #64986 salvage (gateway: multiplex primary bot token scope) + "[redacted:email]": "evefromwayback", # PR #64611 salvage (agent: never load install-tree AGENTS.md as project context) "[email protected]": "2751738943", # PR #54785 salvage (tui: post-turn completion ownership routing) "[email protected]": "Burgunthy", # PR #20096 salvage (gateway: profile-based routing for inbound messages) "[email protected]": "webtecnica", # PR #63360 salvage (nous: restore inference-api base_url) -
tests/agent/test_prompt_builder.py@@ -708,6 +708,44 @@ def test_loads_agents_md(self, tmp_path): assert "Ruff for linting" in result assert "Project Context" in result + def test_skips_agents_md_in_install_tree_on_fallback(self, monkeypatch, tmp_path): + # A backend that FALLS BACK into the install tree (cwd=None → getcwd, + # the desktop default) must not load that tree's contributor AGENTS.md + # as project context. The guard keys off the package root, so point it + # at a fake tree holding an AGENTS.md and getcwd into it. + import agent.runtime_cwd as rt + + monkeypatch.setattr(rt, "_PACKAGE_ROOT", tmp_path.resolve()) + (tmp_path / "AGENTS.md").write_text("Never give up on the right solution.") + monkeypatch.chdir(tmp_path) + result = build_context_files_prompt(cwd=None, skip_soul=True) + assert "Never give up" not in result + assert result == "" + + def test_loads_agents_md_in_install_tree_when_explicit(self, monkeypatch, tmp_path): + # An EXPLICIT cwd pointing at the install tree is a deliberate user + # choice (developing Hermes) — discovery must still run. + import agent.runtime_cwd as rt + + monkeypatch.setattr(rt, "_PACKAGE_ROOT", tmp_path.resolve()) + (tmp_path / "AGENTS.md").write_text("Never give up on the right solution.") + result = build_context_files_prompt(cwd=str(tmp_path), skip_soul=True) + assert "Never give up" in result + + def test_loads_agents_md_in_install_tree_fallback_for_cli(self, monkeypatch, tmp_path): + # CLI/TUI surfaces launch from the user's shell cwd, so an in-tree + # fallback there is deliberate — allow_install_tree_fallback=True + # (system_prompt.py passes it for platform cli/tui) keeps discovery on. + import agent.runtime_cwd as rt + + monkeypatch.setattr(rt, "_PACKAGE_ROOT", tmp_path.resolve()) + (tmp_path / "AGENTS.md").write_text("Never give up on the right solution.") + monkeypatch.chdir(tmp_path) + result = build_context_files_prompt( + cwd=None, skip_soul=True, allow_install_tree_fallback=True + ) + assert "Never give up" in result + def test_loads_cursorrules(self, tmp_path): (tmp_path / ".cursorrules").write_text("Always use type hints.") result = build_context_files_prompt(cwd=str(tmp_path)) -
tests/agent/test_runtime_cwd.py@@ -66,12 +66,21 @@ def test_returns_none_when_unset(self, monkeypatch): monkeypatch.delenv("TERMINAL_CWD", raising=False) assert resolve_context_cwd() is None - def test_returns_nonexistent_dir_unguarded(self, monkeypatch, tmp_path): - # Deliberate asymmetry vs resolve_agent_cwd: context discovery has no isdir - # guard, so a missing dir is returned (not None) — discovery just finds nothing. + def test_returns_none_for_nonexistent_dir(self, monkeypatch, tmp_path): + # A configured but missing dir must not be returned. It previously was, + # which diverged from resolve_agent_cwd and let an invalid cwd steer + # context discovery. Now it is validated and drops to None. missing = tmp_path / "gone" monkeypatch.setenv("TERMINAL_CWD", str(missing)) - assert resolve_context_cwd() == missing + assert resolve_context_cwd() is None + + def test_returns_install_tree_when_explicitly_configured(self, monkeypatch): + # An EXPLICITLY configured install-tree cwd is honored verbatim — the + # Hermes source tree is a legitimate workspace when the user is + # developing Hermes. Only the fallback path (cwd=None → os.getcwd()) + # is policed, in build_context_files_prompt (#64590). + monkeypatch.setenv("TERMINAL_CWD", str(rt._PACKAGE_ROOT)) + assert resolve_context_cwd() == rt._PACKAGE_ROOT def test_expands_leading_tilde(self, monkeypatch): monkeypatch.setenv("TERMINAL_CWD", "~") -
tests/agent/test_system_prompt.py@@ -31,7 +31,10 @@ def _captured_context_cwd(agent): """The cwd build_system_prompt_parts hands to build_context_files_prompt.""" captured = {} - def fake_context_files(cwd=None, skip_soul=False, context_length=None): + def fake_context_files( + cwd=None, skip_soul=False, context_length=None, + allow_install_tree_fallback=False, + ): captured["cwd"] = cwd return ""
-
3 PRs closed without merging
From the close comments: done elsewhere 2 · merged via another change 1
-
#64603 fix(agent): guard context-file discovery against Hermes install tree · done elsewhere →
#64590 -
#64611 fix(agent): never load the install-tree AGENTS.md as project context · merged via another change →
#65485 -
#65542 fix(agent): never load the install-tree AGENTS.md from the unconfigured-cwd fallback · done elsewhere →
3351399,244f70a,#65485
Closed issues · 1
- Closed#64590[Bug]: Context-file discovery falls back to the Hermes install tree and loads its contributor AGENTS.md as project context
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.26: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #65485
- Closed issues
- 1
- Attempts
- 3 PRs closed without merging
- Fix link checked
- 16 Jul 2026