Skip to content

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

Closed
liuhao1024 wants to merge 3 commits into
NousResearch:mainfrom
liuhao1024:fix/issue-20856-branch-sessions-vanish
Closed

fix(state): keep /branch sessions visible after parent reopen#20864
liuhao1024 wants to merge 3 commits into
NousResearch:mainfrom
liuhao1024:fix/issue-20856-branch-sessions-vanish

Conversation

@liuhao1024

@liuhao1024 liuhao1024 commented May 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Branch sessions created via /branch (aka /fork) become invisible in hermes sessions list and the TUI resume list after the parent session is reopened and re-ended.

Root Cause

list_sessions_rich() with include_children=False (the default) filters out all sessions that have parent_session_id set:

where_clauses.append("s.parent_session_id IS NULL")

Branch sessions have parent_session_id linking them to the parent. When the parent is later reopened (reopen_session() clears end_reason) and re-ended with a different reason (e.g. tui_shutdown overwriting branched), the branch child has no way to distinguish itself from subagent/compression children and stays permanently hidden.

The data is still in SQLite — users just can't find it through normal UX.

Related Issue

N/A

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • See commit messages for detailed changes

How to Test

  1. Run pytest tests/ -q — all tests should pass
  2. Verify the specific scenario described above is resolved

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.4.1

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture and workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A

liuhao1024 and others added 3 commits April 24, 2026 22:14
…INSTALL_TIMEOUT

Increase the default npm install timeout for WhatsApp bridge from 60s
to 300s (5 minutes) to accommodate slower systems like Unraid NAS.
Make it configurable via WHATSAPP_NPM_INSTALL_TIMEOUT environment variable
for users who need even longer timeouts.

Closes NousResearch#14980
- Add 'path', 'old_string', 'new_string', and 'patch' to required list
- Update description to clarify mode-specific parameter requirements
- This addresses issue where LLMs would omit these parameters because
  they were not marked as required in the schema, even though they
  are required depending on the mode

Fixes NousResearch#15524
Branch sessions have parent_session_id set, so list_sessions_rich()
with include_children=False (the default) filters them out.  When the
parent is later reopened and re-ended (e.g. tui_shutdown overwriting
branched), the branch child becomes permanently invisible through
normal UX — data is in SQLite but unreachable without direct queries.

Fix: store a _branched_from marker in the child session's model_config
JSON at branch-creation time.  list_sessions_rich() now includes
sessions that have this marker even when include_children=False.
The marker is stable regardless of what happens to the parent's
end_reason.

Changes:
- hermes_state.py: update include_children filter to recognise
  _branched_from in model_config via json_extract()
- cli.py: _handle_branch_command() stores _branched_from in
  model_config when creating the branch session
- gateway/run.py: same for the gateway /branch handler
- tests: 5 new regression tests (3 in test_hermes_state, 2 in
  test_branch_command) covering visibility, parent-reopen survival,
  and non-branch-child exclusion

Fixes NousResearch#20856
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery platform/whatsapp WhatsApp Business adapter tool/file File tools (read, write, patch, search) labels May 6, 2026
@Bartok9

Bartok9 commented May 7, 2026

Copy link
Copy Markdown
Contributor

Reviewed the approach — using json_extract(s.model_config, '$._branched_from') as the stable visibility marker is cleaner than relying on end_reason = 'branched', which the issue correctly identifies as fragile (gets overwritten on parent reopen/re-end).

One observation: existing branch sessions created before this fix (i.e., sessions that already have parent_session_id set but don't have _branched_from in model_config) will still be invisible. The fix only helps newly created branches. This is the correct trade-off — migrating all existing branch sessions would be complex and might miss edge cases.

Worth noting in the PR description that existing pre-fix branch sessions are not retroactively recovered — users who hit the issue would need to hermes sessions list --include-children or check the DB directly. Minor note; doesn't block the fix.

kshitijk4poor pushed a commit to kshitijk4poor/hermes-agent that referenced this pull request Jun 4, 2026
/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 pushed a commit that referenced this pull request Jun 4, 2026
/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.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Salvaged onto current main via #39214 (merged) — your _branched_from marker approach was reimplemented with authorship preserved (you show as the commit author), and closes #20856.

This PR bundled three fixes; here's the disposition of each:

  • /branch visibility — salvaged. I extended it to the TUI session.branch path (tui_gateway/server.py), which the original only covered for CLI + gateway. The TUI branch never ends its parent as branched, so it was hidden from the moment of creation — the marker now surfaces it. Added regression tests for both the reopen+re-end scenario and TUI marker persistence.
  • WhatsApp npm timeout — already on main identically (1a4e8f704), so dropped as redundant.
  • patch tool required list — dropped: marking the mode-specific params (path/old_string/new_string/patch) as unconditionally required contradicts the schema's own 'mode-specific' description and would break strict validators (no single call supplies all of them).

Thanks for the fix and the clear root-cause writeup!

davidgut1982 pushed a commit to davidgut1982/hermes-agent that referenced this pull request Jun 5, 2026
/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.
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
/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.
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
/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.
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 P2 Medium — degraded but workaround exists platform/whatsapp WhatsApp Business adapter tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants