Branch/fork session visibility and isolation bugs
NousResearch/hermes-agent · cluster 14854 · dashboard
Data as of
3 open items. Fix merged in #16150. Check it covers your case.
The short answer
- Known?
- Yes. 10 related items grouped here, 3 issues already closed.
- Fix event
- A fix was merged to main in PR #16150. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #16150. That is separate from the fix event. - Still open
- 2 issues and 1 PR.
Next step: read #16150, then check whether your report is one of the 2 still open.
Read #16150Pick up a Lab taskMultiple bugs and fixes around branched and forked sessions: missing markers, incorrect session list visibility, wrong transcript loading, and session isolation failures in both CLI and REST API contexts.
Open issues2
Open PRs1
- PR#68544fix(api): preserve REST fork session isolation
Fixes on record
-
#16150 fix(/branch): redirect session_log_file and expose branch sessions in list (#14854)
files:
cli.py,hermes_state.py,scripts/release.py,tests/cli/test_branch_command.py,tests/test_hermes_state.pyShow the diff
-
cli.py@@ -4915,6 +4915,12 @@ def _handle_branch_command(self, cmd_original: str) -> None: if self.agent: self.agent.session_id = new_session_id self.agent.session_start = now + # Redirect the JSON session log to the new branch session file so + # messages written after branching land in the correct file. + if hasattr(self.agent, "session_log_file") and hasattr(self.agent, "logs_dir"): + self.agent.session_log_file = ( + self.agent.logs_dir / f"session_{new_session_id}.json" + ) self.agent.reset_session_state() if hasattr(self.agent, "_last_flushed_db_idx"): self.agent._last_flushed_db_idx = len(self.conversation_history) -
hermes_state.py@@ -832,7 +832,18 @@ def list_sessions_rich( params = [] if not include_children: - where_clauses.append("s.parent_session_id IS NULL") + # Show root sessions and branch sessions (whose parent ended with + # end_reason='branched' before the child was created), while still + # hiding sub-agent runs and compression continuations (which also + # carry a parent_session_id but were spawned while the parent was + # still live — i.e., started_at < parent.ended_at). + where_clauses.append( + "(s.parent_session_id IS NULL" + " OR EXISTS (SELECT 1 FROM sessions p" + " WHERE p.id = s.parent_session_id" + " AND p.end_reason = 'branched'" + " AND s.started_at >= p.ended_at))" + ) if source: where_clauses.append("s.source = ?") -
scripts/release.py@@ -70,6 +70,7 @@ "[redacted:email]": "keiravoss94", "[redacted:email]": "stablegenius49", "[redacted:email]": "EKKOLearnAI", + "[redacted:email]": "octo-patch", "[redacted:email]": "simbam99", "[redacted:email]": "irispillars", "[redacted:email]": "stablegenius49", -
tests/cli/test_branch_command.py@@ -160,6 +160,30 @@ def test_branch_syncs_agent(self, cli_instance, session_db): assert agent.reset_session_state.called assert agent._last_flushed_db_idx == 4 # len(conversation_history) + def test_branch_updates_agent_session_log_file(self, cli_instance, session_db, tmp_path): + """Branching must redirect the agent's session_log_file to the new session's path.""" + from cli import HermesCLI + from pathlib import Path + + logs_dir = tmp_path / "sessions" + logs_dir.mkdir() + + agent = MagicMock() + agent._last_flushed_db_idx = 0 + agent.logs_dir = logs_dir + agent.session_log_file = logs_dir / f"session_{cli_instance.session_id}.json" + cli_instance.agent = agent + + old_log_file = agent.session_log_file + HermesCLI._handle_branch_command(cli_instance, "/branch") + + new_session_id = cli_instance.session_id + expected_log = logs_dir / f"session_{new_session_id}.json" + assert agent.session_log_file == expected_log, ( + "session_log_file must point to the branch session, not the original" + ) + assert agent.session_log_file != old_log_file + def test_branch_sets_resumed_flag(self, cli_instance, session_db): """Branch should set _resumed=True to prevent auto-title generation.""" from cli import HermesCLI -
tests/test_hermes_state.py@@ -1485,6 +1485,48 @@ def test_preview_newlines_collapsed(self, db): assert "\n" not in sessions[0]["preview"] assert "Line one Line two" in sessions[0]["preview"] + def test_branch_session_visible_in_list(self, db): + """Branch sessions (parent ended with 'branched') must appear in list_sessions_rich.""" + db.create_session("parent", "cli") + db.end_session("parent", "branched") + db.create_session("branch", "cli", parent_session_id="parent") + db.append_message("branch", "user", "Exploring the alternative approach") + + sessions = db.list_sessions_rich() + ids = [s["id"] for s in sessions] + assert "branch" in ids, "Branch session should be visible in default list" + + def test_subagent_session_still_hidden(self, db): + """Sub-agent children (parent NOT ended with 'branched') remain hidden.""" + db.create_session("root", "cli") + db.create_session("delegate", "cli", parent_session_id="root") + + sessions = db.list_sessions_rich() + ids = [s["id"] for s in sessions] + assert "delegate" not in ids, "Delegate sub-agent should not appear in default list" + assert "root" in ids + + def test_compression_child_still_hidden(self, db): + """Compression continuation sessions remain hidden (parent ended with 'compression').""" + import time as _time + t0 = _time.time() + db.create_session("root", "cli") + db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0, "root")) + db._conn.execute( + "UPDATE sessions SET ended_at=?, end_reason='compression' WHERE id=?", + (t0 + 1800, "root"), + ) + db._conn.commit() + db.create_session("continuation", "cli", parent_session_id="root") + db._conn.execute( + "UPDATE sessions SET started_at=? WHERE id=?", (t0 + 1801, "continuation") + ) + db._conn.commit() + + sessions = db.list_sessions_rich(project_compression_tips=False) + ids = [s["id"] for s in sessions] + assert "continuation" not in ids, "Compression continuation should stay hidden" + class TestCompressionChainProjection: """Tests for lineage-aware list_sessions_rich — compressed conversations
-
-
#117013 fix(dashboard): resuming a chat after a gateway restart never lands inside a hidden subagent row (#115092)
files:
hermes_cli/web_server_sessions.py,tests/hermes_cli/test_web_server.py
2 PRs closed without merging
From the close comments: done elsewhere 1 · other 1
Closed issues · 3
- Closed#14854/branch creates SQLite entry but not JSON file
- Closed#64618Dashboard: resuming a session that spawned subagents loads the subagent's transcript (`_session_latest_descendant` lacks delegate/branch/tool guards)
- Closed#68510REST /api/sessions/{id}/fork child missing _branched_from marker — resolve_resume_session_id() hijacks the original session's reads
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.15: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab