From 21d5df410094cc2dfd64e597880c8665a1feefb2 Mon Sep 17 00:00:00 2001 From: chelsealong Date: Wed, 9 Sep 2026 02:29:21 +0000 Subject: [PATCH] fix(session-state): keep the canonical Bot Chat hidden when pinned set_session_pinned's unconditional hidden-clear (0be7f931) also unhides the canonical Bot Chat, which the desktop contract requires to stay hidden and reachable only through the bot row. Exposing it breaks the sidebar and disables the rename guard that protects its identity title. Skip the unhide when the pinned row is hidden and carries the exact canonical title; ordinary hidden sessions are unaffected. --- hermes_state_sessions.py | 12 ++++++++++-- tests/test_hermes_state.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/hermes_state_sessions.py b/hermes_state_sessions.py index 6155d2d6ef..7974b3cccf 100644 --- a/hermes_state_sessions.py +++ b/hermes_state_sessions.py @@ -878,10 +878,18 @@ class SessionSessionsMixin: def set_session_pinned(self, session_id: str, pinned: bool) -> bool: """Pin/unpin a session and its compression lineage (pins are exempt from the auto_archive sweep). Pinning also clears ``hidden``: a pin means "keep this visible", and a hidden+pinned row is - otherwise absent from both the default listing and the pinned back-fill (see #106171).""" + otherwise absent from both the default listing and the pinned back-fill (see #106171). + Exempt the canonical Bot Chat (hidden + exact registry title): the desktop contract keeps it + hidden and reachable only through the bot row, and unhiding it would also disable the + rename guard in ``_set_session_title`` that protects its identity (see review on #106180).""" result = self._set_lineage_column("pinned", session_id, int(pinned)) if pinned: - self._set_lineage_column("hidden", session_id, 0) + row = self.get_session(session_id) + is_canonical_bot_chat = bool(row) and bool(row.get("hidden")) and ( + (row.get("title") or "") == self.CANONICAL_BOT_CHAT_TITLE + ) + if not is_canonical_bot_chat: + self._set_lineage_column("hidden", session_id, 0) return result def set_session_hidden(self, session_id: str, hidden: bool) -> bool: diff --git a/tests/test_hermes_state.py b/tests/test_hermes_state.py index 6793e80bff..a3013deac8 100644 --- a/tests/test_hermes_state.py +++ b/tests/test_hermes_state.py @@ -4476,7 +4476,17 @@ class TestSessionPinAndStaleArchive: listed_ids = [s["id"] for s in db.list_sessions_rich(min_message_count=1)] assert "s1" in listed_ids + def test_pinning_the_canonical_bot_chat_leaves_it_hidden(self, db): + """The canonical Bot Chat (hidden + exact registry title) is desktop-owned and must stay + hidden even when pinned, or it leaks into the Sessions sidebar and loses its rename guard + (review on #106180). Unlike an ordinary hidden session, pinning must not clear ``hidden``.""" + db.create_session(session_id="bot1", source="desktop") + db.set_session_title("bot1", db.CANONICAL_BOT_CHAT_TITLE) + db.set_session_hidden("bot1", True) + db.set_session_pinned("bot1", True) + + assert db.get_session("bot1")["hidden"] == 1 # ── pinned back-fill past the page window ───────────────────────────── def test_pinned_session_survives_the_limit_window(self, db):