{"repo":"NousResearch/hermes-agent","public_id":"12884","display_label":"CLI auto-scroll and refresh interval fixes","summary":"Multiple issues and fixes address unwanted automatic scrolling to the bottom in CLI/TUI mode, primarily caused by refresh_interval behavior. One unrelated fix handles a UnicodeDecodeError during post-update config migration.","subsystem":"hermes_cli","counts":{"members":9,"open_issues":2,"open_issues_distinct":2,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":5},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":49056},"resolution":{"state":"partially_resolved","by":"pr-49056"},"freshness":{"state":"verified","last_verified_at":"2026-06-19T14:06:34+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.18666666666666665},"fix_edges":[{"issue_number":48309,"pr_number":49056,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":12884,"title":"[Bug]: The CLI window always automatically rotate to the bottom","url":null,"state":"open"},{"kind":"issue","number":48309,"title":"cli`: `refresh_interval=1.0` causes terminal auto-scroll-to-bottom in non-fullscreen mode","url":null,"state":"closed"},{"kind":"issue","number":53636,"title":"Fix: CLI auto-scrolls to bottom every second on Termux/Android (cli_refresh_interval=1.0)","url":null,"state":"open"},{"kind":"pr","number":12886,"title":"fix(update): handle UnicodeDecodeError in post-update config migration prompt","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#68497","number":68497},{"kind":"pr","value":"#68497","number":68497}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":12982,"title":"fix(ui-tui): prevent stickyScroll re-activation after manual scroll-up","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":48319,"title":"fix(cli): make refresh_interval configurable, default to 0 (disabled)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#48321","number":48321},{"kind":"pr","value":"#48321","number":48321},{"kind":"pr","value":"#48319","number":48319},{"kind":"pr","value":"#48321","number":48321},{"kind":"pr","value":"#48321","number":48321}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":48321,"title":"fix(cli): disable idle refresh_interval in non-fullscreen mode to prevent auto-scroll","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#48319","number":48319},{"kind":"pr","value":"#48309","number":48309},{"kind":"pr","value":"#48319","number":48319}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":49056,"title":"fix(cli): make idle refresh_interval configurable, default 0 (no auto-scroll)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py","hermes_cli/config.py","scripts/release.py","tests/hermes_cli/test_config.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -13527,13 +13527,13 @@ def _get_voice_status():\n             style=style,\n             full_screen=False,\n             mouse_support=False,\n-            # The status bar contains wall-clock read-outs (live prompt elapsed\n-            # and idle-since-last-turn). Once a turn finishes there may be no\n-            # further events to invalidate the app, so prompt_toolkit would keep\n-            # rendering the first post-turn value (usually ``✓ 0s``) forever.\n-            # A low-rate refresh keeps the clock honest without reintroducing a\n-            # custom repaint thread or touching conversation state.\n-            refresh_interval=1.0,\n+            # Read from display.cli_refresh_interval (default 0 = disabled).\n+            # When non-zero, prompt_toolkit redraws the UI on this cadence\n+            # during idle, keeping wall-clock status-bar read-outs ticking.\n+            # Set to 0 to suppress background redraws entirely — avoids\n+            # fighting terminal auto-scroll in non-fullscreen mode (Xshell,\n+            # iTerm2, Windows Terminal). See #48309.\n+            refresh_interval=float(CLI_CONFIG.get(\"display\", {}).get(\"cli_refresh_interval\", 0)),\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"]},{"file":"hermes_cli/config.py","hunks":["@@ -1581,6 +1581,12 @@ def _ensure_hermes_home_managed(home: Path):\n         # TUI busy indicator style: kaomoji (default), emoji, unicode (braille\n         # spinner), or ascii.  Live-swappable via `/indicator <style>`.\n         \"tui_status_indicator\": \"kaomoji\",\n+        # Seconds between prompt_toolkit redraws in the classic CLI when idle.\n+        # 0 = disabled (no background refresh — the pre-0.15.2 behaviour).\n+        # Positive values e.g. 1.0 keep wall-clock status-bar read-outs\n+        # (idle-since-last-turn) ticking but may fight terminal auto-scroll in\n+        # non-fullscreen mode on some emulators (Xshell, iTerm2, etc.).\n+        \"cli_refresh_interval\": 0,\n         \"user_message_preview\": {  # CLI: how many submitted user-message lines to echo back in scrollback\n             \"first_lines\": 2,\n             \"last_lines\": 2,"]},{"file":"scripts/release.py","hunks":["@@ -1531,6 +1531,7 @@\n     \"[redacted:email]\": \"erikengervall\",  # PR #28774 (firecrawl integration tag)\n     \"[redacted:email]\": \"egilewski\",  # PR #30432 (MEDIA path traversal fix, GHSA-jmf9-9729-7pp8)\n     \"[redacted:email]\": \"McClean-Edison\",  # PR #29817 (register_auxiliary_task plugin API)\n+    \"[redacted:email]\": \"OYLFLMH\",  # PR #48312 salvage (cli_refresh_interval config, #48309)\n     \"[redacted:email]\": \"SamuelZ12\",  # PR #7480 (show recap after in-session resume)\n     \"[redacted:email]\": \"daizhonggeng\",  # PR #9020 (numbered /resume selection)\n     \"[redacted:email]\": \"wanwan2qq\",  # PR #10215 (strip brackets/quotes from /resume; gateway session-ID lookup)"]},{"file":"tests/hermes_cli/test_config.py","hunks":["@@ -955,6 +955,16 @@ def test_migrate_to_v15_adds_interim_assistant_message_gate(self, tmp_path):\n         assert raw[\"display\"][\"interim_assistant_messages\"] is True\n \n \n+class TestCliRefreshIntervalConfig:\n+    \"\"\"Test the CLI refresh_interval config default (#48309).\"\"\"\n+\n+    def test_default_config_disables_cli_refresh_interval(self):\n+        \"\"\"cli_refresh_interval defaults to 0 (disabled) to avoid\n+        background redraws that fight terminal auto-scroll-on-output\n+        in non-fullscreen mode (Xshell, iTerm2, Windows Terminal).\"\"\"\n+        assert DEFAULT_CONFIG[\"display\"][\"cli_refresh_interval\"] == 0\n+\n+\n class TestDiscordChannelPromptsConfig:\n     def test_default_config_includes_discord_channel_prompts(self):\n         assert DEFAULT_CONFIG[\"discord\"][\"channel_prompts\"] == {}\n"]}]},{"kind":"pr","number":74742,"title":"fix(ui-tui): prevent stickyScroll re-activation after manual scroll-up","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#75439","number":75439},{"kind":"pr","value":"#12884","number":12884},{"kind":"pr","value":"#75439","number":75439}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}