From 1e2ad17afd50fd21d55e8e5980f35f376db15a14 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:41:06 +0530 Subject: [PATCH] fix: descendant CTE must dedup (UNION) to survive parent-chain cycles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #39140 CTE used UNION ALL, which recurses forever if a corrupted parent chain loops (a -> b -> a) — reproduced: query never returns. The old Python walk was cycle-safe via a seen-set. UNION dedups the working set and terminates. Regression test added and mutation-verified (UNION ALL hangs the test, UNION passes). --- hermes_cli/web_server.py | 2 +- tests/hermes_cli/test_web_server.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index c5978ebfff4..5645f91ddf7 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -9306,7 +9306,7 @@ def _session_latest_descendant(session_id: str, db): """ WITH RECURSIVE descendants(id, parent_session_id, started_at) AS ( SELECT id, parent_session_id, started_at FROM sessions WHERE id = ? - UNION ALL + UNION SELECT s.id, s.parent_session_id, s.started_at FROM sessions s JOIN descendants d ON s.parent_session_id = d.id diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 748800ca1b2..c67371b4e33 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -1138,6 +1138,29 @@ class TestWebServerEndpoints: assert worker_resp.status_code == 200 assert worker_resp.json()["session_id"] == "worker-tip" + def test_latest_descendant_survives_parent_cycle(self): + """Regression for the #39140 CTE salvage: a corrupted parent chain + that loops (a -> b -> a) must terminate (UNION dedup) instead of + recursing forever like UNION ALL would.""" + from hermes_state import SessionDB + + db = SessionDB() + try: + db.create_session(session_id="cyc-a", source="cli") + db.create_session( + session_id="cyc-b", source="cli", parent_session_id="cyc-a" + ) + db._conn.execute( + "UPDATE sessions SET parent_session_id='cyc-b' WHERE id='cyc-a'" + ) + db._conn.commit() + finally: + db.close() + + resp = self.client.get("/api/sessions/cyc-a/latest-descendant") + assert resp.status_code == 200 + assert resp.json()["session_id"] == "cyc-b" + def test_analytics_endpoints_read_requested_profile(self): from hermes_state import SessionDB from hermes_cli import profiles as profiles_mod