{"repo":"NousResearch/hermes-agent","public_id":"38252","display_label":"CLI display artifacts when agent session ends","summary":"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.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":2,"attempt_prs":0},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":38906},"resolution":{"state":"partially_resolved","by":"pr-38906"},"freshness":{"state":"verified","last_verified_at":"2026-06-04T11:38:36+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.3933333333333333},"fix_edges":[{"issue_number":38252,"pr_number":38906,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":38252,"pr_number":38928,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":38252,"title":"[Bug]: Hermes Agent does not cleanly hand control back to CLI after session ends","url":null,"state":"closed"},{"kind":"issue","number":59257,"title":"[Bug]: Desktop-first commits silently degrading CLI/TUI — session title lost on exit (PR #57000)","url":null,"state":"closed"},{"kind":"pr","number":38906,"title":"fix(cli): erase live chrome on exit so it isn't stranded above the session summary","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -14874,6 +14874,17 @@ def _get_voice_status():\n             style=style,\n             full_screen=False,\n             mouse_support=False,\n+            # Erase the live bottom chrome (status bar, input box, separator\n+            # rules) on exit instead of freezing a final copy into scrollback.\n+            # Without this, prompt_toolkit's render_as_done teardown repaints\n+            # the chrome one last time and leaves it stranded above the exit\n+            # summary — so a dead status bar + empty prompt sit between the\n+            # conversation transcript and the \"Resume this session\" block, and\n+            # stack with the next session's UI on resume (#38252). The actual\n+            # conversation transcript is printed through patch_stdout into\n+            # normal scrollback and is unaffected; only the managed chrome is\n+            # erased. Applies to every exit path (/exit, /quit, EOF, Ctrl+C).\n+            erase_when_done=True,\n             **({'cursor': _STEADY_CURSOR} if _STEADY_CURSOR is not None else {}),\n         )\n         _disable_prompt_toolkit_cpr_warning(app)\n"]}]},{"kind":"pr","number":38928,"title":"fix(cli): clear screen on exit so live chrome isn't stranded in scrollback","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -12727,8 +12727,53 @@ def run_agent():\n             if tts_thread is not None and tts_thread.is_alive():\n                 tts_thread.join(timeout=5)\n     \n+    def _clear_terminal_on_exit(self):\n+        \"\"\"Clear screen + scrollback so nothing is stranded above the exit summary.\n+\n+        Called from ``_print_exit_summary`` after ``app.run()`` has returned and\n+        prompt_toolkit has torn down its renderer + restored terminal modes —\n+        so a direct write to the real stdout fd is safe (the StdoutProxy /\n+        patch_stdout layer is gone by now).\n+\n+        Sequence: ``ESC[3J`` (erase scrollback) + ``ESC[2J`` (erase visible\n+        screen) + ``ESC[H`` (cursor home). Modern terminals on Linux, macOS and\n+        Windows (Terminal / conhost with VT processing, which prompt_toolkit\n+        already enables) all honor these. Best-effort: skip silently when\n+        stdout isn't a real console, and fall back to the platform ``clear`` /\n+        ``cls`` command if the escape write fails.\n+        \"\"\"\n+        try:\n+            stream = sys.stdout\n+            if stream is None or not stream.isatty():\n+                return\n+        except Exception:\n+            return\n+        try:\n+            stream.write(\"\\033[3J\\033[2J\\033[H\")\n+            stream.flush()\n+            return\n+        except Exception:\n+            pass\n+        # Fallback: shell clear command (rarely needed — escapes work on every\n+        # VT-capable terminal, but this covers exotic stdout wrappers).\n+        try:\n+            os.system(\"cls\" if os.name == \"nt\" else \"clear\")\n+        except Exception:\n+            pass\n+\n     def _print_exit_summary(self):\n         \"\"\"Print session resume info on exit, similar to Claude Code.\"\"\"\n+        # Clear the screen + scrollback before printing the summary so the\n+        # live bottom chrome (status bar, input box, separator rules) and the\n+        # rest of the session transcript don't get stranded above the exit\n+        # summary (#38252). By this point app.run() has returned and\n+        # prompt_toolkit has restored terminal modes, so writing raw escapes\n+        # to stdout is safe. ESC[3J clears scrollback, ESC[2J clears the\n+        # visible screen, ESC[H homes the cursor — so the summary prints at a\n+        # clean top-left. Falls back to the platform clear command if stdout\n+        # isn't a TTY-capable stream. Honors NO_COLOR/dumb terminals by\n+        # skipping silently when there's no real console.\n+        self._clear_terminal_on_exit()\n         print()\n         msg_count = len(self.conversation_history)\n         if msg_count > 0:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}