From 6dfb8326f58b2845a8b17134be00160fd69c9ddd Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:02:09 -0700 Subject: [PATCH] fix(state): exclude delegate/branch/tool children from resume walk + reconcile salvaged fixes Follow-up to the salvage of #45035 + #48682. The two PRs touched different functions (resolve_resume_session_id vs get_compression_tip) but #45035's descendant walk followed ANY parent_session_id child, so a delegate/subagent child could hijack the resume target. Apply the same _branched_from / _delegate_from / source!='tool' exclusion the rest of hermes_state.py uses, so the resume walk only follows genuine compression continuations. Also updates the unrealistic delegation test fixture to carry the real _delegate_from marker, and updates 3 list_sessions_rich test mocks for the order_by_last_active kwarg #48682 added. AUTHOR_MAP: map PINKIIILQWQ + ailang323 salvage authors. --- hermes_state.py | 10 +++++++++- scripts/release.py | 3 +++ tests/hermes_state/test_resolve_resume_session_id.py | 10 +++++++++- tests/test_tui_gateway_server.py | 6 +++--- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/hermes_state.py b/hermes_state.py index 0c56152456b..66255663058 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -3198,11 +3198,19 @@ class SessionDB: if row is not None: best = current - # Walk to the most-recently-started child. + # Walk to the most-recently-started child — but skip explicit + # branch (`_branched_from`), delegate/subagent (`_delegate_from`), + # and tool children. They also carry a ``parent_session_id`` yet + # are NOT compression continuations; following them would hijack + # the resume target to an unrelated session (e.g. a subagent + # run). This mirrors the child-exclusion in ``get_compression_tip``. try: child_row = self._conn.execute( "SELECT id FROM sessions " "WHERE parent_session_id = ? " + " AND json_extract(COALESCE(model_config, '{}'), '$._branched_from') IS NULL " + " AND json_extract(COALESCE(model_config, '{}'), '$._delegate_from') IS NULL " + " AND COALESCE(source, '') != 'tool' " "ORDER BY started_at DESC, id DESC LIMIT 1", (current,), ).fetchone() diff --git a/scripts/release.py b/scripts/release.py index bca02cf0845..a075a096e46 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -81,6 +81,9 @@ AUTHOR_MAP = { "joaomarcosdias444@gmail.com": "JoaoMarcos44", "286497132+srojk34@users.noreply.github.com": "srojk34", "srojk34@users.noreply.github.com": "srojk34", # legacy prefix-less noreply (PR #50098 salvage; #38763) + "pinkiilqwq@users.noreply.github.com": "PINKIIILQWQ", # PR #45035 salvage (resume-to-tip; #38763) + "pink@PinkdeMacBook-Air.local": "PINKIIILQWQ", # PR #45035 local git identity (resume-to-tip; #38763) + "ailang323@163.com": "ailang323", # PR #48682 salvage (compression-tip predicate; #38763) "59806492+sitkarev@users.noreply.github.com": "sitkarev", "zheng@omegasys.eu": "omegazheng", "220877172+james47kjv@users.noreply.github.com": "james47kjv", diff --git a/tests/hermes_state/test_resolve_resume_session_id.py b/tests/hermes_state/test_resolve_resume_session_id.py index 2f53b1b8a72..f0cb3f0a07f 100644 --- a/tests/hermes_state/test_resolve_resume_session_id.py +++ b/tests/hermes_state/test_resolve_resume_session_id.py @@ -116,7 +116,15 @@ def test_compression_tip_not_confused_with_delegation_child(db): base = int(time.time()) - 10_000 db.create_session("conv", source="cli") db.append_message("conv", role="user", content="parent turn") - db.create_session("subagent", source="cli", parent_session_id="conv") + # Real delegate/subagent sessions carry the `_delegate_from` marker + # (set in delegate_tool.py) — that marker, not timing, is what + # distinguishes them from a compression continuation. + db.create_session( + "subagent", + source="subagent", + parent_session_id="conv", + model_config={"_delegate_from": "conv"}, + ) db.append_message("subagent", role="assistant", content="delegated work") conn = db._conn assert conn is not None diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index fe42ebcc232..698b4c0d45f 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -6100,7 +6100,7 @@ def test_session_most_recent_returns_first_non_denied(monkeypatch): """Drops `tool` rows like session.list does, returns the first hit.""" class _DB: - def list_sessions_rich(self, *, source=None, limit=200): + def list_sessions_rich(self, *, source=None, limit=200, order_by_last_active=False): return [ {"id": "tool-1", "source": "tool", "title": "noise", "started_at": 100}, {"id": "tui-1", "source": "tui", "title": "real", "started_at": 99}, @@ -6119,7 +6119,7 @@ def test_session_most_recent_returns_first_non_denied(monkeypatch): def test_session_most_recent_returns_null_when_only_tool_rows(monkeypatch): class _DB: - def list_sessions_rich(self, *, source=None, limit=200): + def list_sessions_rich(self, *, source=None, limit=200, order_by_last_active=False): return [{"id": "tool-1", "source": "tool", "started_at": 1}] monkeypatch.setattr(server, "_get_db", lambda: _DB()) @@ -6137,7 +6137,7 @@ def test_session_most_recent_folds_db_exception_into_null_result(monkeypatch): 'no answer' (Copilot review on #17130).""" class _BrokenDB: - def list_sessions_rich(self, *, source=None, limit=200): + def list_sessions_rich(self, *, source=None, limit=200, order_by_last_active=False): raise RuntimeError("db locked") monkeypatch.setattr(server, "_get_db", lambda: _BrokenDB())