NousResearch/hermes-agent
Cross-profile session routing bug and fixes
Cluster 58032 · gateway. Data as of
Nothing open on GitHub. Fix merged in #59325 on 7 Jul 2026. Check it covers your case.
The short answer
0open issues
0open PRs
3items grouped
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #59325. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #59325. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A bug was identified where disabling multiplex_profiles leaves orphaned sessions that cause incorrect profile routing. Two related fix attempts address cross-profile session recovery in the gateway.
Open issues0
Open PRs0
Fixes on record
-
#59325 fix(gateway): avoid cross-profile session recovery (#58119 salvage)
files:
gateway/session.py,tests/gateway/test_multiplex_phase0.pyShow the diff
-
gateway/session.py@@ -1225,6 +1225,45 @@ def _resolve_profile_for_key(self, source: Optional[SessionSource] = None) -> Op except Exception: return None + @staticmethod + def _profile_from_session_key(session_key: Optional[str]) -> Optional[str]: + """Extract the profile namespace encoded in a gateway session key.""" + if not session_key: + return None + parts = str(session_key).split(":") + if len(parts) < 2 or parts[0] != "agent": + return None + namespace = parts[1] or "main" + return "default" if namespace == "main" else namespace + + @staticmethod + def _active_profile_name() -> str: + try: + from hermes_cli.profiles import get_active_profile_name + return get_active_profile_name() or "default" + except Exception: + return "default" + + def _recovered_row_allowed_for_active_profile( + self, + *, + requested_session_key: str, + recovered: Dict[str, Any], + ) -> bool: + """Prevent non-multiplexed gateways from reviving another profile's row.""" + if getattr(self.config, "multiplex_profiles", False): + return True + + recovered_key = str(recovered.get("session_key") or "") + if not recovered_key or recovered_key == requested_session_key: + return True + + recovered_profile = self._profile_from_session_key(recovered_key) + if recovered_profile is None: + return True + + return recovered_profile == self._active_profile_name() + def _generate_session_key(self, source: SessionSource) -> str: """Generate a session key from a source.""" return build_session_key( @@ -1285,6 +1324,18 @@ def _recover_session_from_db( return None if not recovered: return None + if not self._recovered_row_allowed_for_active_profile( + requested_session_key=session_key, + recovered=recovered, + ): + logger.warning( + "Gateway session DB recovery ignored %s for %s because " + "multiplex_profiles is disabled and the row belongs to a " + "different profile", + recovered.get("session_key"), + session_key, + ) + return None try: self._db.reopen_session(str(recovered["id"])) except Exception as exc: -
tests/gateway/test_multiplex_phase0.py@@ -9,6 +9,7 @@ on. """ import pytest +from datetime import datetime from unittest.mock import patch import yaml @@ -201,3 +202,66 @@ def test_flag_on_default_profile_stays_legacy(self, tmp_path): with patch("hermes_cli.profiles.get_active_profile_name", return_value="default"): assert store._generate_session_key(s) == "agent:main:telegram:dm:99" + +class _RecoveringDB: + def __init__(self, row): + self.row = row + self.reopened = [] + + def find_latest_gateway_session_for_peer(self, **_kwargs): + return self.row + + def reopen_session(self, session_id): + self.reopened.append(session_id) + + +class TestSessionStoreUnmultiplexedRecovery: + """Turning multiplexing off must not recover another profile's session.""" + + def _store_with_row(self, tmp_path, row, **cfg_kw): + config = GatewayConfig(**cfg_kw) + with patch("gateway.session.SessionStore._ensure_loaded"): + store = SessionStore(sessions_dir=tmp_path, config=config) + store._db = _RecoveringDB(row) + store._loaded = True + return store + + def test_flag_off_rejects_other_profile_peer_fallback(self, tmp_path): + row = { + "id": "sess-coder", + "started_at": 1700000000, + "session_key": "agent:coder:telegram:dm:99", + } + store = self._store_with_row(tmp_path, row) + source = _src(chat_id="99", chat_type="dm") + + with patch("hermes_cli.profiles.get_active_profile_name", return_value="default"): + recovered = store._recover_session_from_db( + session_key="agent:main:telegram:dm:99", + source=source, + now=datetime.fromtimestamp(1700000001), + ) + + assert recovered is None + assert store._db.reopened == [] + + def test_flag_off_allows_active_profile_peer_fallback(self, tmp_path): + row = { + "id": "sess-coder", + "started_at": 1700000000, + "session_key": "agent:coder:telegram:dm:99", + } + store = self._store_with_row(tmp_path, row) + source = _src(chat_id="99", chat_type="dm") + + with patch("hermes_cli.profiles.get_active_profile_name", return_value="coder"): + recovered = store._recover_session_from_db( + session_key="agent:main:telegram:dm:99", + source=source, + now=datetime.fromtimestamp(1700000001), + ) + + assert recovered is not None + assert recovered.session_id == "sess-coder" + assert recovered.session_key == "agent:main:telegram:dm:99" + assert store._db.reopened == ["sess-coder"]
-
1 PR closed without merging
From the close comments: done elsewhere 1
-
#58119 fix(gateway): avoid cross-profile session recovery · done elsewhere →
f1fde49,#59325
Closed issues · 1
- Closed#58032Bug: multiplex_profiles: false leaves orphaned sessions that cause wrong profile routing
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.67: 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
- gateway
- Merged PRs
- #59325
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 7 Jul 2026