System error messages appearing with wrong role assignment
NousResearch/hermes-agent · cluster 2228 · agent
Data as of
Nothing open on GitHub. Fix merged in #2303. Check it covers your case.
The short answer
- Known?
- Yes. 6 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #2303. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and fixes concern a bug where system-generated error messages were being injected with a dynamic or incorrect role (appearing as role=user instead of role=assistant). The cluster focuses on correcting role assignment for system error messages and related session resume behavior.
Open issues0
Open PRs0
Fixes on record
-
#2303 fix: remove synthetic error message injection, fix session resume after repeated failures
files:
hermes_cli/plugins.py,run_agent.py,tests/test_plugins.py,website/docs/reference/environment-variables.md,website/docs/user-guide/features/plugins.mdShow the diff
-
hermes_cli/plugins.py@@ -5,7 +5,8 @@ Discovers, loads, and manages plugins from three sources: 1. **User plugins** – ``~/.hermes/plugins/<name>/`` -2. **Project plugins** – ``./.hermes/plugins/<name>/`` +2. **Project plugins** – ``./.hermes/plugins/<name>/`` (opt-in via + ``HERMES_ENABLE_PROJECT_PLUGINS``) 3. **Pip plugins** – packages that expose the ``hermes_agent.plugins`` entry-point group. @@ -62,6 +63,11 @@ _NS_PARENT = "hermes_plugins" +def _env_enabled(name: str) -> bool: + """Return True when an env var is set to a truthy opt-in value.""" + return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"} + + # --------------------------------------------------------------------------- # Data classes # --------------------------------------------------------------------------- @@ -186,8 +192,9 @@ def discover_and_load(self) -> None: manifests.extend(self._scan_directory(user_dir, source="user")) # 2. Project plugins (./.hermes/plugins/) - project_dir = Path.cwd() / ".hermes" / "plugins" - manifests.extend(self._scan_directory(project_dir, source="project")) + if _env_enabled("HERMES_ENABLE_PROJECT_PLUGINS"): + project_dir = Path.cwd() / ".hermes" / "plugins" + manifests.extend(self._scan_directory(project_dir, source="project")) # 3. Pip / entry-point plugins manifests.extend(self._scan_entry_points()) -
run_agent.py@@ -6807,20 +6807,18 @@ def _stop_spinner(): pending_handled = True break - if not pending_handled: - # Error happened before tool processing (e.g. response parsing). - # Choose role to avoid consecutive same-role messages. - last_role = messages[-1].get("role") if messages else None - err_role = "assistant" if last_role == "user" else "user" - sys_err_msg = { - "role": err_role, - "content": f"[System error during processing: {error_msg}]", - } - messages.append(sys_err_msg) - + # Non-tool errors don't need a synthetic message injected. + # The error is already printed to the user (line above), and + # the retry loop continues. Injecting a fake user/assistant + # message pollutes history, burns tokens, and risks violating + # role-alternation invariants. + # If we're near the limit, break to avoid infinite loops if api_call_count >= self.max_iterations - 1: final_response = f"I apologize, but I encountered repeated errors: {error_msg}" + # Append as assistant so the history stays valid for + # session resume (avoids consecutive user messages). + messages.append({"role": "assistant", "content": final_response}) break if final_response is None and ( -
tests/test_plugins.py@@ -67,6 +67,7 @@ def test_discover_project_plugins(self, tmp_path, monkeypatch): project_dir = tmp_path / "project" project_dir.mkdir() monkeypatch.chdir(project_dir) + monkeypatch.setenv("HERMES_ENABLE_PROJECT_PLUGINS", "true") plugins_dir = project_dir / ".hermes" / "plugins" _make_plugin_dir(plugins_dir, "proj_plugin") @@ -76,6 +77,19 @@ def test_discover_project_plugins(self, tmp_path, monkeypatch): assert "proj_plugin" in mgr._plugins assert mgr._plugins["proj_plugin"].enabled + def test_discover_project_plugins_skipped_by_default(self, tmp_path, monkeypatch): + """Project plugins are not discovered unless explicitly enabled.""" + project_dir = tmp_path / "project" + project_dir.mkdir() + monkeypatch.chdir(project_dir) + plugins_dir = project_dir / ".hermes" / "plugins" + _make_plugin_dir(plugins_dir, "proj_plugin") + + mgr = PluginManager() + mgr.discover_and_load() + + assert "proj_plugin" not in mgr._plugins + def test_discover_is_idempotent(self, tmp_path, monkeypatch): """Calling discover_and_load() twice does not duplicate plugins.""" plugins_dir = tmp_path / "hermes_test" / "plugins" -
website/docs/reference/environment-variables.md@@ -232,6 +232,7 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe | `HERMES_QUIET` | Suppress non-essential output (`true`/`false`) | | `HERMES_API_TIMEOUT` | LLM API call timeout in seconds (default: `900`) | | `HERMES_EXEC_ASK` | Enable execution approval prompts in gateway mode (`true`/`false`) | +| `HERMES_ENABLE_PROJECT_PLUGINS` | Enable auto-discovery of repo-local plugins from `./.hermes/plugins/` (`true`/`false`, default: `false`) | | `HERMES_BACKGROUND_NOTIFICATIONS` | Background process notification mode in gateway: `all` (default), `result`, `error`, `off` | | `HERMES_EPHEMERAL_SYSTEM_PROMPT` | Ephemeral system prompt injected at API-call time (never persisted to sessions) | -
website/docs/user-guide/features/plugins.md@@ -22,6 +22,8 @@ Drop a directory into `~/.hermes/plugins/` with a `plugin.yaml` and Python code: Start Hermes — your tools appear alongside built-in tools. The model can call them immediately. +Project-local plugins under `./.hermes/plugins/` are disabled by default. Enable them only for trusted repositories by setting `HERMES_ENABLE_PROJECT_PLUGINS=true` before starting Hermes. + ## What plugins can do | Capability | How | @@ -38,7 +40,7 @@ Start Hermes — your tools appear alongside built-in tools. The model can call | Source | Path | Use case | |--------|------|----------| | User | `~/.hermes/plugins/` | Personal plugins | -| Project | `.hermes/plugins/` | Project-specific plugins | +| Project | `.hermes/plugins/` | Project-specific plugins (requires `HERMES_ENABLE_PROJECT_PLUGINS=true`) | | pip | `hermes_agent.plugins` entry_points | Distributed packages | ## Available hooks
-
4 PRs closed without merging
From the close comments: closed without comment 2 · done elsewhere 2
-
#2231 fix: always use role=assistant for system error messages · done elsewhere →
#2303 - #2236 fix(agent): always use role=assistant for system error messages, never role=user · closed without comment
-
#2253 fix: always use assistant role for system error messages · done elsewhere →
#2303 - #2263 fix: bug: system error messages injected with dynamic role, can appear · closed without comment
Closed issues · 1
- Closed#2228bug: system error messages injected with dynamic role, can appear as role=user
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.33: 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
- #2303
- Closed issues
- 1
- Attempts
- 4 PRs closed without merging
- Fix link checked
- 21 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.