diff --git a/hermes_state_sessions.py b/hermes_state_sessions.py index 2f2c6b2e9d..1e44841716 100644 --- a/hermes_state_sessions.py +++ b/hermes_state_sessions.py @@ -1218,7 +1218,11 @@ class SessionSessionsMixin: exclude_sources=exclude_sources, cwd_prefix=cwd_prefix, min_message_count=min_message_count, archived_only=archived_only, include_archived=include_archived, ) - if not include_hidden: + # The archived-only view is the recovery surface for rows that dropped out of every + # default list: a session that is archived AND hidden (Bot Mode marks its sessions + # hidden) must still be reachable there, or nothing but direct DB access can bring + # it back (#90946). + if not include_hidden and not archived_only: where_clauses.append("s.hidden = 0") where_sql = _where_sql(where_clauses) base_where_params = list(params) # pinned back-fill reuses the WHERE before LIMIT/OFFSET diff --git a/tests/hermes_state/test_session_archiving.py b/tests/hermes_state/test_session_archiving.py index 36ecb95a17..2d5fbac1eb 100644 --- a/tests/hermes_state/test_session_archiving.py +++ b/tests/hermes_state/test_session_archiving.py @@ -49,3 +49,18 @@ def test_unarchiving_compression_tip_unarchives_projected_root(db): assert db.get_session("root")["archived"] == 0 assert db.get_session("tip")["archived"] == 0 assert [s["id"] for s in db.list_sessions_rich(order_by_last_active=True)] == ["tip"] + + +def test_archived_only_view_includes_hidden_archived_sessions(db): + """The archived-only view is the recovery surface: a session that is both + archived and hidden (Bot Mode marks its sessions hidden) must appear + there, otherwise it is unreachable from every UI list (#90946).""" + db.create_session("plain", source="cli") + db.create_session("both", source="cli") + assert db.set_session_hidden("both", True) is True + assert db.set_session_archived("both", True) is True + + # Default list: hidden rows stay excluded (unchanged behaviour)... + assert [s["id"] for s in db.list_sessions_rich(order_by_last_active=True)] == ["plain"] + # ...and the archived-only view must surface the archived+hidden row. + assert [s["id"] for s in db.list_sessions_rich(order_by_last_active=True, archived_only=True)] == ["both"]