From cecf2767ee256f0c52a54dabca46d54c624b99bb Mon Sep 17 00:00:00 2001 From: joelbrilliant Date: Sun, 5 Jul 2026 06:18:42 +1000 Subject: [PATCH] fix(gateway): preserve lazy reset after session expiry --- gateway/run.py | 2 +- tests/gateway/test_clean_shutdown_marker.py | 27 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/gateway/run.py b/gateway/run.py index cb84b33793a9..b72f82993ee0 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -6186,7 +6186,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew """ if agent is None: return - if context.startswith("shutdown"): + if context.startswith("shutdown") or context == "session expiry": try: agent._end_session_on_close = False except Exception: diff --git a/tests/gateway/test_clean_shutdown_marker.py b/tests/gateway/test_clean_shutdown_marker.py index dc07b25b0b36..62c5da6d33a6 100644 --- a/tests/gateway/test_clean_shutdown_marker.py +++ b/tests/gateway/test_clean_shutdown_marker.py @@ -245,6 +245,33 @@ class TestCleanShutdownMarker: assert agent._end_session_on_close is False agent.close.assert_called_once() + def test_session_expiry_cleanup_preserves_lazy_reset_boundary(self, tmp_path, monkeypatch): + """Session expiry cleanup must not turn an expired chat into an agent_close row. + + The expiry watcher only tears down cached resources. The next inbound + message owns the reset boundary, creating a fresh session with the + normal auto-reset notice. If cleanup lets ``agent.close()`` end the + SQLite row as ``agent_close``, stale-route recovery treats it as + recoverable and resurrects the expired session instead. + """ + monkeypatch.setattr("gateway.run._hermes_home", tmp_path) + from gateway.run import GatewayRunner + + runner = object.__new__(GatewayRunner) + agent = MagicMock() + agent._end_session_on_close = True + + async def _run(): + await GatewayRunner._cleanup_agent_resources_off_loop( + runner, agent, context="session expiry" + ) + + import asyncio + asyncio.get_event_loop().run_until_complete(_run()) + + assert agent._end_session_on_close is False + agent.close.assert_called_once() + # --------------------------------------------------------------------------- # resume_pending freshness gate (#46934)