fix(state): keep /branch sessions visible after parent reopen

/branch (aka /fork) sessions vanished from /resume and /sessions. Both
surfaces funnel through list_sessions_rich(include_children=False), which
hid any session with a parent_session_id unless identified as a branch via a
heuristic — parent.end_reason == 'branched' AND child.started_at >=
parent.ended_at.

Two ways that heuristic failed:
1. CLI/gateway branches: once the parent was reopened (e.g. resumed) and
   re-ended with a different end_reason (tui_shutdown overwriting 'branched'),
   the heuristic stopped matching and the branch was hidden permanently.
2. TUI branches (tui_gateway session.branch): the TUI never ends the parent
   as 'branched' — it creates the child while the parent is still live — so
   the heuristic NEVER matched and TUI branches were hidden from the moment
   they were created (this is the macOS desktop app's primary symptom).

Fix: persist a stable '_branched_from' marker in the branch session's
model_config at creation time across ALL THREE branch paths (CLI cli.py,
gateway gateway/run.py, and TUI tui_gateway/server.py), and OR a
json_extract(model_config, '$._branched_from') IS NOT NULL check into the
list_sessions_rich filter. The marker is immutable across the parent's
lifecycle, so the branch stays visible regardless of how/whether the parent
is ended. The legacy end_reason heuristic is kept (OR'd) so pre-existing
branches remain visible. Subagent/compression children (no marker, parent
not 'branched') stay correctly hidden. Fixes #20856.

Approach by liuhao1024 (PR #20864); reimplemented on current main, extended
to the TUI branch path (which the original missed), with regression tests for
the reopen+re-end scenario and the TUI marker persistence.
This commit is contained in:
liuhao1024 2026-06-04 22:12:21 +05:30 committed by kshitij
parent d1367355d5
commit a3fb48b2ce
6 changed files with 136 additions and 7 deletions

View file

@ -2716,6 +2716,44 @@ class TestListSessionsRich:
ids = [s["id"] for s in sessions]
assert "branch" in ids, "Branch session should be visible in default list"
def test_branch_session_visible_after_parent_reopen_and_reend(self, db):
"""Branch sessions stay visible after the parent is reopened and re-ended.
Regression for issue #20856: /branch (aka /fork) sessions vanished from
/resume and /sessions once the parent was reopened (e.g. resumed) and
re-ended with a different end_reason tui_shutdown overwriting
'branched' which broke the legacy end_reason heuristic. The stable
_branched_from marker in model_config keeps them visible.
"""
import json as _json
db.create_session("parent", "cli")
db.end_session("parent", "branched")
db.create_session(
"branch",
"cli",
model_config={"_branched_from": "parent"},
parent_session_id="parent",
)
db.append_message("branch", "user", "Exploring the alternative approach")
# Marker is persisted at creation time.
branch_row = db.get_session("branch")
cfg = _json.loads(branch_row["model_config"]) if branch_row["model_config"] else {}
assert cfg.get("_branched_from") == "parent"
# Visible immediately after branching.
assert "branch" in [s["id"] for s in db.list_sessions_rich()]
# Parent reopened + re-ended with a different reason (the bug trigger).
db.reopen_session("parent")
db.end_session("parent", "tui_shutdown")
# Branch must STILL be visible — the marker survives the parent's
# end_reason churn, unlike the legacy 'branched' heuristic.
ids = [s["id"] for s in db.list_sessions_rich()]
assert "branch" in ids, "Branch should stay visible after parent re-end"
def test_subagent_session_still_hidden(self, db):
"""Sub-agent children (parent NOT ended with 'branched') remain hidden."""
db.create_session("root", "cli")