# NousResearch/hermes-agent · Branch/fork session visibility and isolation bugs

## Open now

- 2 issues, 1 PR open on GitHub.
- #39471: \[Bug\]: branch-like child sessions can disappear from \`sessions list\` when \`_branched_from\` is missing
- #68511: REST /api/sessions/{id}/fork child missing _branched_from marker — resolve_resume_session_id() hijacks the original session's reads
- PR #68544: fix(api): preserve REST fork session isolation

Multiple 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.

## TL;DR

- 10 member(s): 2 open issue(s), 3 closed issue(s), 1 open PR(s), 2 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #16150. Verify it addresses your case.
- **Recommended:** read PR #16150 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-16150`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #39471: \[Bug\]: branch-like child sessions can disappear from \`sessions list\` when \`_branched_from\` is missing
- #68511: REST /api/sessions/{id}/fork child missing _branched_from marker — resolve_resume_session_id() hijacks the original session's reads

## Closed / duplicate issues

- #14854: /branch creates SQLite entry but not JSON file
- #64618: Dashboard: resuming a session that spawned subagents loads the subagent's transcript (\`_session_latest_descendant\` lacks delegate/branch/tool guards)
- #68510: REST /api/sessions/{id}/fork child missing _branched_from marker — resolve_resume_session_id() hijacks the original session's reads

## Prior & competing PRs

- **#16150: fix(/branch): redirect session_log_file and expose branch sessions in list (#14854)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`, `hermes_state.py`, `scripts/release.py`, `tests/cli/test_branch_command.py`, `tests/test_hermes_state.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/web_server_sessions.py`, `tests/hermes_cli/test_web_server.py`
- **#14896: fix(/branch): redirect session_log_file and expose branch sessions in sessions list**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#16150`
- **#64620: fix(cli): exclude delegate/branch/tool children from dashboard latest-descendant walk**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `b80292c9df`, `#117013`, `#64618`
- **#68544: fix(api): preserve REST fork session isolation**. open
  - maintainer direction: category `silent_close`; implication `active`
  - files: `gateway/platforms/api_server.py`, `tests/gateway/test_session_api.py`

## Fix status

- Merged to main in PR #16150 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-09-20`.
- Subsystem: `dashboard`
