Resume recap clutter from tool calls and fixed limits
Cluster 4337 · hermes cli. Data as of
Nothing open on GitHub. Fix merged in #31695 on 24 May 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #31695. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #31695. That is separate from the fix event. - Still open
- Nothing open on GitHub.
One bug report and two pull requests concern the recap shown when resuming a hermes CLI session. They discuss tool-call clutter, hardcoded truncation limits, skipping tool-call-only entries, making limits configurable, and showing the recap after an in-session /resume.
Open issues0
Open PRs0
Fixes on record
-
#31695 fix(cli): show recap after in-session /resume + tunable recap limits
files:
cli.py,hermes_cli/config.py,scripts/release.py,tests/cli/test_resume_display.pyShow the diff
-
cli.py@@ -415,6 +415,12 @@ def load_cli_config() -> Dict[str, Any]: "display": { "compact": False, "resume_display": "full", + # Recap tuning for /resume — see hermes_cli/config.py DEFAULT_CONFIG. + "resume_exchanges": 10, + "resume_max_user_chars": 300, + "resume_max_assistant_chars": 200, + "resume_max_assistant_lines": 3, + "resume_skip_tool_only": True, "show_reasoning": False, "streaming": True, "busy_input_mode": "interrupt", @@ -5088,10 +5094,13 @@ def _display_resumed_history(self): if self.resume_display == "minimal": return - MAX_DISPLAY_EXCHANGES = 10 # max user+assistant pairs to show - MAX_USER_LEN = 300 # truncate user messages - MAX_ASST_LEN = 200 # truncate assistant text - MAX_ASST_LINES = 3 # max lines of assistant text + # Read limits from config (with hardcoded defaults) + _disp = CLI_CONFIG.get("display", {}) + MAX_DISPLAY_EXCHANGES = int(_disp.get("resume_exchanges", 10)) + MAX_USER_LEN = int(_disp.get("resume_max_user_chars", 300)) + MAX_ASST_LEN = int(_disp.get("resume_max_assistant_chars", 200)) + MAX_ASST_LINES = int(_disp.get("resume_max_assistant_lines", 3)) + SKIP_TOOL_ONLY = _disp.get("resume_skip_tool_only", True) # Collect displayable entries (skip system, tool-result messages) entries = [] # list of (role, display_text) @@ -5154,6 +5163,10 @@ def _display_resumed_history(self): if not parts: # Skip pure-reasoning messages that have no visible output continue + # Skip tool-call-only entries when SKIP_TOOL_ONLY is enabled + has_text = bool(text) + if SKIP_TOOL_ONLY and not has_text and tool_calls: + continue entries.append(("assistant", " ".join(parts))) _last_asst_idx = len(entries) - 1 _last_asst_full = " ".join(full_parts) @@ -6617,6 +6630,7 @@ def _handle_resume_command(self, cmd_original: str) -> None: f" ({msg_count} user message{'s' if msg_count != 1 else ''}," f" {len(self.conversation_history)} total)" ) + self._display_resumed_history() else: _cprint(f" ↻ Resumed session {target_id}{title_part} — no messages, starting fresh.") -
hermes_cli/config.py@@ -1008,6 +1008,19 @@ def _ensure_hermes_home_managed(home: Path): "compact": False, "personality": "kawaii", "resume_display": "full", + # Recap tuning for /resume and startup resume. The defaults match the + # historical hardcoded values; expose them as config so power users can + # widen or tighten the snapshot to taste. + "resume_exchanges": 10, # max user+assistant pairs to show + "resume_max_user_chars": 300, # truncate user message text + "resume_max_assistant_chars": 200, # truncate non-last assistant text + "resume_max_assistant_lines": 3, # truncate non-last assistant lines + # When True (default), assistant entries that are *only* tool calls + # (no visible text) are skipped in the recap. This prevents the recap + # from being dominated by `[2 tool calls: terminal, read_file]` lines + # when an exchange was tool-heavy. Set False to restore the legacy + # behavior of showing tool-call summaries inline. + "resume_skip_tool_only": True, "busy_input_mode": "interrupt", # interrupt | queue | steer # When true, `hermes --tui` auto-resumes the most recent human- # facing session on launch instead of forging a fresh one. -
scripts/release.py@@ -1278,6 +1278,7 @@ "[redacted:email]": "erikengervall", # PR #28774 (firecrawl integration tag) "[redacted:email]": "egilewski", # PR #30432 (MEDIA path traversal fix, GHSA-jmf9-9729-7pp8) "[redacted:email]": "McClean-Edison", # PR #29817 (register_auxiliary_task plugin API) + "[redacted:email]": "SamuelZ12", # PR #7480 (show recap after in-session resume) } -
tests/cli/test_resume_display.py@@ -155,14 +155,34 @@ def test_tool_messages_hidden(self): assert "Page content" not in output def test_tool_calls_shown_as_summary(self): - cli = _make_cli() + # Disable tool-only skip so the summary line is rendered for this fixture. + cli = _make_cli(config_overrides={"display": {"resume_skip_tool_only": False}}) cli.conversation_history = _tool_call_history() - output = self._capture_display(cli) + import cli as _cli_mod + # CLI_CONFIG is read at call-time inside _display_resumed_history, so + # apply the override for the duration of the capture, not just at init. + with patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": { + "display": {"resume_skip_tool_only": False, "resume_display": "full"} + }}): + output = self._capture_display(cli) assert "2 tool calls" in output assert "web_search" in output assert "web_extract" in output + def test_tool_only_message_skipped_by_default(self): + """Assistant messages with only tool_calls (no text) are skipped when + resume_skip_tool_only=True (the default). The summary line is hidden. + """ + cli = _make_cli() + cli.conversation_history = _tool_call_history() + output = self._capture_display(cli) + + # The tool-only assistant entry should be skipped + assert "2 tool calls" not in output + # The final text reply should still appear + assert "Here are some great Python tutorials" in output + def test_long_user_message_truncated(self): cli = _make_cli() long_text = "A" * 500 @@ -611,6 +631,55 @@ def test_singular_user_message_grammar(self): assert "1 user messages" not in output +# ── Tests for _handle_resume_command recap display ─────────────────── + + +class TestHandleResumeCommandRecap: + """In-session /resume should show the same recap panel as startup resume.""" + + def test_resume_command_displays_recap_when_messages_restored(self): + cli = _make_cli() + cli.session_id = "current_session" + messages = _simple_history() + + mock_db = MagicMock() + mock_db.get_session.return_value = {"id": "target_session", "title": "Test Session"} + mock_db.get_messages_as_conversation.return_value = messages + # resolve_resume_session_id passes the id through when no compression chain. + mock_db.resolve_resume_session_id.return_value = "target_session" + cli._session_db = mock_db + + with ( + patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="target_session"), + patch.object(cli, "_display_resumed_history") as display_mock, + ): + cli._handle_resume_command("/resume test session") + + assert cli.session_id == "target_session" + assert cli.conversation_history == messages + mock_db.end_session.assert_called_once_with("current_session", "resumed_other") + mock_db.reopen_session.assert_called_once_with("target_session") + display_mock.assert_called_once_with() + + def test_resume_command_skips_recap_when_session_has_no_messages(self): + cli = _make_cli() + cli.session_id = "current_session" + + mock_db = MagicMock() + mock_db.get_session.return_value = {"id": "target_session", "title": None} + mock_db.get_messages_as_conversation.return_value = [] + mock_db.resolve_resume_session_id.return_value = "target_session" + cli._session_db = mock_db + + with ( + patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="target_session"), + patch.object(cli, "_display_resumed_history") as display_mock, + ): + cli._handle_resume_command("/resume target_session") + + display_mock.assert_not_called() + + # ── Integration: _init_agent skips when preloaded ────────────────────
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#4434 fix(cli): skip tool-call-only entries in resume recap, expose limits as config options · merged via another change →
#31695
Closed issues · 1
- Closed#4337[Bug] hermes -c resume recap is unusable in tool-heavy sessions: tool-call clutter and hardcoded truncation limits
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: 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 LabAt a glance
- Subsystem
- hermes cli
- Merged PRs
- #31695
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 24 May 2026