Skip to content

fix(state): keep /branch sessions visible after parent reopen (salvage #20864)#39214

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/branch-session-visibility
Jun 4, 2026
Merged

fix(state): keep /branch sessions visible after parent reopen (salvage #20864)#39214
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/branch-session-visibility

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Salvages the substantive fix from #20864 by @liuhao1024 onto current main and closes #20856.

The bug: /branch (aka /fork) creates a new numbered branch session, but it vanishes from /resume and /sessions. Both surfaces funnel through list_sessions_rich(include_children=False), which hides any session with a parent_session_id unless it's identified as a branch via a heuristic — parent.end_reason == 'branched' AND child.started_at >= parent.ended_at.

That heuristic fails in two distinct ways:

  1. CLI/gateway branches — work right after branching, but reopen_session() clears end_reason and a later end_session(parent, 'tui_shutdown') overwrites 'branched'. After the parent is resumed once and re-closed (routine), the heuristic stops matching and the branch is hidden permanently.
  2. TUI branches (tui_gateway session.branch, the macOS desktop app's path) — 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 almost certainly the primary user-visible symptom.

The fix: persist a stable _branched_from marker in the branch session's model_config at creation time across all three branch paths (cli.py, gateway/run.py, and 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 (or whether) the parent is ended. The legacy end_reason='branched' heuristic is kept (OR'd) so branch sessions created before this change remain visible — no backward-compat regression. Subagent/compression children (no marker, parent not 'branched') stay correctly hidden.

Closes #20856.

Why a reimplementation rather than a cherry-pick

#20864 was a bundle of three unrelated fixes, 4826 commits behind main, and conflicting. Triaged each:

  • WhatsApp npm timeout — already on main identically (1a4e8f704). Dropped.
  • patch tool required list — marks mode-specific params as unconditionally required, contradicting its own "mode-specific" description; would break strict schema validators. Dropped.
  • /branch visibility — the valuable one, salvaged here. The original PR only patched the CLI + gateway paths; this PR additionally covers the TUI path, which the original missed and which is the desktop app's primary surface.

The original couldn't cherry-pick (the /branch handlers and the list_sessions_rich filter all moved on main), so @liuhao1024's _branched_from approach was reimplemented on current main. Commit is attributed to them; the TUI extension and regression tests are the maintainer follow-up.

Verification

Reproduced the bug on current main across all paths, then confirmed the fix end-to-end (isolated HERMES_HOME, real SessionDB, and a handler-level E2E driving the actual session.branch RPC):

scenario main this PR
fresh CLI/gateway branch visible visible
CLI/gateway branch after parent reopen + re-end hidden (bug) visible
TUI branch, parent still live hidden (bug) visible
TUI branch after parent tui_shutdown hidden (bug) visible
legacy branch, no marker visible visible
subagent/compression child hidden hidden
include_children=True shows all shows all
  • tests/test_hermes_state.py::...::test_branch_session_visible_after_parent_reopen_and_reendfails on main (assert 'branch' in ['parent']), passes here.
  • tests/tui_gateway/test_protocol.py::test_session_branch_persists_branched_from_markerfails on main (KeyError: 'model_config'), passes here. Drives the real session.branch handler and asserts the marker is persisted.
  • tests/tui_gateway/103 passed; tests/test_hermes_state.py + tests/cli/test_branch_command.py272 passed.
  • ruff check clean on all changed files.

Credit

Approach and original fix by @liuhao1024 (#20864). Reimplemented on current main with authorship preserved; TUI branch-path coverage + regression tests added by the maintainer.

/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 NousResearch#20856.

Approach by liuhao1024 (PR NousResearch#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.
@kshitijk4poor kshitijk4poor force-pushed the salvage/branch-session-visibility branch from 700bb1f to 0d281ca Compare June 4, 2026 16:51
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/gateway Gateway runner, session dispatch, delivery labels Jun 4, 2026
@kshitijk4poor kshitijk4poor merged commit a3fb48b into NousResearch:main Jun 4, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/branch (fork) sessions vanish from session listings after parent is closed

3 participants