fix: compare legacy reset children against the parent's started_at, not ended_at

The reopen backfill guard `child.started_at >= parent.ended_at` compared against
the parent's CURRENT end boundary. A genuine markerless legacy reset child whose
parent was later reopened and re-ended has started_at earlier than that second
boundary, so it was no longer frozen with `_reset_from`; once end_reason cleared
it dropped out of /sessions as ephemeral — the multi-cycle gateway-peer shape the
issue describes. Comparing against the parent's started_at still rejects
children that predate the parent while keeping every earlier-boundary reset
child; the marker and source exclusions are unchanged.

Review finding: cycled parent's earlier reset child lost its `_reset_from` stamp and vanished from the session list on reopen.
This commit is contained in:
teknium1
2026-09-14 21:29:03 -07:00
committed by Teknium
parent 61e8f1cd82
commit 5ffdf823ae
2 changed files with 30 additions and 4 deletions
+5 -4
View File
@@ -448,9 +448,10 @@ class SessionSessionsMixin:
return changed
def reopen_session(self, session_id: str) -> None:
"""Clear ended_at/end_reason so a session can be resumed; first freeze only legacy reset children
whose creation order proves they followed the reset. The shared legacy predicate keeps list-time
compatibility broad, while this durable backfill must not overwrite explicit fork provenance."""
"""Clear ended_at/end_reason so a session can be resumed; first freeze markerless legacy reset
children, skipping explicit fork/delegate provenance and children that predate the parent itself.
The guard compares against the parent's started_at, not its current ended_at: a parent that was
reopened and re-ended later still owns reset children from its earlier boundaries."""
def _do(conn):
conn.execute(
"UPDATE sessions AS child SET model_config = json_set("
@@ -459,7 +460,7 @@ class SessionSessionsMixin:
f"AND {_sql_json_extract('child.model_config', '$._branched_from')} IS NULL "
f"AND {_sql_json_extract('child.model_config', '$._delegate_from')} IS NULL "
"AND COALESCE(child.source, '') != 'tool' "
"AND child.started_at >= (SELECT p.ended_at FROM sessions p WHERE p.id = child.parent_session_id) "
"AND child.started_at >= (SELECT p.started_at FROM sessions p WHERE p.id = child.parent_session_id) "
f"AND {_legacy_reset_child_sql('child', _session_ids_placeholders(_RESET_END_REASONS))}",
(session_id, *_RESET_END_REASONS),
)
+25
View File
@@ -2944,6 +2944,31 @@ class TestListSessionsRich:
assert child is not None
assert child["model_config"] is None
def test_reopen_backfills_legacy_reset_child_of_cycled_parent(self, db):
"""A markerless reset child from an earlier boundary is still frozen after the parent was
reopened and re-ended later (its started_at precedes the parent's current ended_at)."""
lane_key = "agent:main:telegram:dm:cycled"
db.create_session("cycled_parent", "telegram", session_key=lane_key)
db.end_session("cycled_parent", "session_reset")
db.create_session(
"cycled_reset_child", "telegram", session_key=lane_key, parent_session_id="cycled_parent"
)
db._conn.execute(
"UPDATE sessions SET ended_at = NULL, end_reason = NULL WHERE id = ?", ("cycled_parent",)
)
db._conn.commit()
db.end_session("cycled_parent", "session_switch")
db._conn.execute(
"UPDATE sessions SET ended_at = ended_at + 100 WHERE id = ?", ("cycled_parent",)
)
db._conn.commit()
db.reopen_session("cycled_parent")
child = db.get_session("cycled_reset_child")
assert json.loads(child["model_config"]) == {"_reset_from": "cycled_parent"}
assert "cycled_reset_child" in [row["id"] for row in db.list_sessions_rich(source="telegram")]
def test_reset_parent_does_not_surface_unrelated_child(self, db):
db.create_session(
"reset_parent",