From ca907480ae3448880464f557e12e0662ec28cb23 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Sun, 12 Jul 2026 21:29:55 +0700 Subject: [PATCH] fix(gateway): allow ws_orphan_reap rows in session recovery (#63207) Whitelist ws_orphan_reap alongside agent_close in find_latest_gateway_session_for_peer so gateway stale-routing self-heal can reopen wrongly-reaped messaging sessions instead of minting empty replacements. Layer A prevention already landed in #60609. --- gateway/session.py | 7 ++++--- hermes_state.py | 11 ++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/gateway/session.py b/gateway/session.py index fea3ba4a3..f04090445 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -1904,9 +1904,10 @@ class SessionStore: # Stale routing self-heal (#54878): the in-memory entry # points at a session that has ALREADY been ended in # state.db. Drop it and fall through to recovery/create. - # Recovery finder reopens ``agent_close`` rows (preserving - # the transcript) but returns None for other end_reasons - # (e.g. /new), starting a fresh session. + # Recovery finder reopens ``agent_close`` and mistaken + # ``ws_orphan_reap`` rows (preserving the transcript) but + # returns None for other end_reasons (e.g. /new), starting + # a fresh session. logger.warning( "gateway.session: routing key %r -> %s is ended in " "state.db but still live in sessions.json; dropping " diff --git a/hermes_state.py b/hermes_state.py index 79e95ffa6..7181266fe 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -2113,9 +2113,10 @@ class SessionDB: pruned after process-level restart bugs. New gateway sessions persist the deterministic ``session_key`` on the durable session row so the mapping can be rebuilt exactly. Rows ended only by older gateway - cleanup's ``agent_close`` bug are treated as recoverable; explicit - conversation boundaries such as /new, /resume switches, and compression - splits are not. + cleanup's ``agent_close`` bug or a mistaken TUI ``ws_orphan_reap`` + (dashboard viewer disconnect before #60609) are treated as recoverable; + explicit conversation boundaries such as /new, /resume switches, and + compression splits are not. """ if not session_key: return None @@ -2125,7 +2126,7 @@ class SessionDB: SELECT * FROM sessions WHERE session_key = ? AND source = ? - AND (ended_at IS NULL OR end_reason = 'agent_close') + AND (ended_at IS NULL OR end_reason IN ('agent_close', 'ws_orphan_reap')) AND (COALESCE(message_count, 0) > 0 OR EXISTS ( SELECT 1 FROM messages WHERE messages.session_id = sessions.id LIMIT 1 )) @@ -2150,7 +2151,7 @@ class SessionDB: AND COALESCE(chat_id, '') = COALESCE(?, '') AND COALESCE(chat_type, '') = COALESCE(?, '') AND COALESCE(thread_id, '') = COALESCE(?, '') - AND (ended_at IS NULL OR end_reason = 'agent_close') + AND (ended_at IS NULL OR end_reason IN ('agent_close', 'ws_orphan_reap')) AND (COALESCE(message_count, 0) > 0 OR EXISTS ( SELECT 1 FROM messages WHERE messages.session_id = sessions.id LIMIT 1 ))