# NousResearch/hermes-agent · CLI display artifacts when agent session ends

## Open now

- Nothing open on GitHub.

Multiple reports and fixes address leftover or misplaced UI elements in the CLI/TUI after an agent session concludes, including stranded live chrome, lost session titles, and the Chat tab not recovering. The dashboard recovery fix is also included.

## TL;DR

- 4 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 2 merged PR(s), 0 PR(s) closed without merging.
- A fix was merged to main in PR #38906. Verify it addresses your case.
- **Recommended:** read PR #38906 (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-38906`. 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.39`: 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.

## Closed / duplicate issues

- #38252: \[Bug\]: Hermes Agent does not cleanly hand control back to CLI after session ends
- #59257: \[Bug\]: Desktop-first commits silently degrading CLI/TUI — session title lost on exit (PR #57000)

## Prior & competing PRs

- **#38906: fix(cli): erase live chrome on exit so it isn't stranded above the session summary**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -14874,6 +14874,17 @@ def _get_voice_status():
                 style=style,
                 full_screen=False,
                 mouse_support=False,
    +            # Erase the live bottom chrome (status bar, input box, separator
    +            # rules) on exit instead of freezing a final copy into scrollback.
    +            # Without this, prompt_toolkit's render_as_done teardown repaints
    +            # the chrome one last time and leaves it stranded above the exit
    +            # summary — so a dead status bar + empty prompt sit between the
    +            # conversation transcript and the "Resume this session" block, and
    +            # stack with the next session's UI on resume (#38252). The actual
    +            # conversation transcript is printed through patch_stdout into
    +            # normal scrollback and is unaffected; only the managed chrome is
    +            # erased. Applies to every exit path (/exit, /quit, EOF, Ctrl+C).
    +            erase_when_done=True,
                 **({'cursor': _STEADY_CURSOR} if _STEADY_CURSOR is not None else {}),
             )
             _disable_prompt_toolkit_cpr_warning(app)
    ```
- **#38928: fix(cli): clear screen on exit so live chrome isn't stranded in scrollback**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -12727,8 +12727,53 @@ def run_agent():
                 if tts_thread is not None and tts_thread.is_alive():
                     tts_thread.join(timeout=5)
         
    +    def _clear_terminal_on_exit(self):
    +        """Clear screen + scrollback so nothing is stranded above the exit summary.
    +
    +        Called from ``_print_exit_summary`` after ``app.run()`` has returned and
    +        prompt_toolkit has torn down its renderer + restored terminal modes —
    +        so a direct write to the real stdout fd is safe (the StdoutProxy /
    +        patch_stdout layer is gone by now).
    +
    +        Sequence: ``ESC[3J`` (erase scrollback) + ``ESC[2J`` (erase visible
    +        screen) + ``ESC[H`` (cursor home). Modern terminals on Linux, macOS and
    +        Windows (Terminal / conhost with VT processing, which prompt_toolkit
    +        already enables) all honor these. Best-effort: skip silently when
    +        stdout isn't a real console, and fall back to the platform ``clear`` /
    +        ``cls`` command if the escape write fails.
    +        """
    +        try:
    +            stream = sys.stdout
    +            if stream is None or not stream.isatty():
    +                return
    +        except Exception:
    +            return
    +        try:
    +            stream.write("\033[3J\033[2J\033[H")
    +            stream.flush()
    +            return
    +        except Exception:
    +            pass
    +        # Fallback: shell clear command (rarely needed — escapes work on every
    +        # VT-capable terminal, but this covers exotic stdout wrappers).
    +        try:
    +            os.system("cls" if os.name == "nt" else "clear")
    +        except Exception:
    +            pass
    +
         def _print_exit_summary(self):
             """Print session resume info on exit, similar to Claude Code."""
    +        # Clear the screen + scrollback before printing the summary so the
    +        # live bottom chrome (status bar, input box, separator rules) and the
    +        # rest of the session transcript don't get stranded above the exit
    +        # summary (#38252). By this point app.run() has returned and
    +        # prompt_toolkit has restored terminal modes, so writing raw escapes
    +        # to stdout is safe. ESC[3J clears scrollback, ESC[2J clears the
    +        # visible screen, ESC[H homes the cursor — so the summary prints at a
    +        # clean top-left. Falls back to the platform clear command if stdout
    +        # isn't a TTY-capable stream. Honors NO_COLOR/dumb terminals by
    +        # skipping silently when there's no real console.
    +        self._clear_terminal_on_exit()
             print()
             msg_count = len(self.conversation_history)
             if msg_count > 0:
    ```

## Fix status

- Merged to main in PR #38906 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-04`.
