hermes-agent/tests/cli
kshitijk4poor 8e71b5136b fix(cli): paint approval/clarify/sudo/secret modal prompts directly, not via the throttle (#41098)
In classic CLI mode the dangerous-command approval prompt (and the clarify,
sudo, and secret-capture prompts) could fail to render: the user saw
'⏱ Timeout — denying command' after 60s without ever seeing the panel,
making approvals.mode: manual unusable.

Root cause. These prompts run their wait loop on the agent/background thread:
they set modal state that a ConditionalContainer's filter reads, then call
self._invalidate() to repaint so the panel appears. _invalidate() is a
THROTTLED wrapper built for high-frequency background repaints (spinner frames,
streaming) — it (a) returns early while a SIGWINCH resize-recovery is pending,
and (b) otherwise only repaints if 250ms elapsed since the last paint. Under
either condition the modal's entry paint is silently dropped, the
ConditionalContainer never re-evaluates, and the prompt times out unseen.

The throttle never belonged on these paths. Originally the callbacks painted
with a direct self._app.invalidate() and worked; a throttle PR blanket-replaced
every invalidate (including these rare, one-shot, user-blocking modal paints)
with the throttled _invalidate(); a later commit removed an idle 1Hz repaint
that had been masking dropped modal paints, surfacing the bug. Notably the
modal KEY-BINDING handlers (↑/↓/Enter) already paint with a direct
event.app.invalidate(), never the throttle — the background-thread callbacks
were the inconsistent ones.

Fix. Add a small _paint_now() helper that paints directly (guarded for a
missing _app, exception-safe) and route the four modal paths' entry, response,
countdown, and teardown paints through it — matching the key-handler idiom.
This covers approval, clarify, sudo, and the secret-capture teardown
(_submit_secret_response, which previously used the throttled _invalidate() so
its panel could linger after submit). _invalidate() is left untouched and its
docstring now states it is for high-frequency background repaints only;
modal/interactive paints must use _paint_now()/_app.invalidate() directly. This
also fixes the resize-recovery edge case for free (a direct paint never
consults the resize guard) without a throttle-bypass flag that could be
cargo-culted onto hot paths. Countdown refresh cadence tightened 5s->1s so the
timer stays visible while waiting, and a copy-pasted duplicate countdown block
in _clarify_callback is removed.

Tests: TestModalPaintNow drives all three wait-loop callbacks on a background
thread with BOTH gates active (_resize_recovery_pending=True + a recent
_last_invalidate in the throttle window) and asserts the panel paints on entry
AND repaints on teardown; plus a secret-teardown test, a direct
_paint_now-vs-_invalidate gate test, and a no-_app safety test. Each modal test
fails if its paint is reverted to _invalidate(). 17 in-file tests pass; full
tests/cli suite green (900).

Diagnosis credit: the throttle-drop root cause was identified by @sanidhyasin
in #41116; @islam666 independently reached the same direct-invalidate approach
in #41166; original report #41098 by @jodonnel.
2026-06-08 00:46:43 +05:30
..
__init__.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_bracketed_paste_timeout.py fix(cli): bracketed-paste timeout prevents permanent input freeze (#16263) 2026-05-25 05:07:11 -07:00
test_branch_command.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_busy_input_mode_command.py feat(busy): add 'steer' as a third display.busy_input_mode option (#16279) 2026-04-26 18:21:29 -07:00
test_cli_approval_ui.py fix(cli): paint approval/clarify/sudo/secret modal prompts directly, not via the throttle (#41098) 2026-06-08 00:46:43 +05:30
test_cli_background_status_indicator.py feat(cli): show live background terminal-process count in status bar (#32061) 2026-05-25 05:35:02 -07:00
test_cli_background_tui_refresh.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_cli_bracketed_paste_sanitizer.py fix(cli): strip leaked bracketed-paste wrappers 2026-04-26 21:47:40 -07:00
test_cli_browser_connect.py test(cli): cover Brave binary CDP launch detection 2026-05-19 22:34:05 -07:00
test_cli_context_warning.py fix(context): align guidance with 64k minimum 2026-05-24 23:23:12 -07:00
test_cli_copy_command.py feat: add /copy and /agents 2026-04-09 17:19:36 -05:00
test_cli_extension_hooks.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_cli_external_editor.py feat(cli): add editor workflow for drafts 2026-04-20 02:53:40 -07:00
test_cli_file_drop.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_cli_force_redraw.py fix(cli): clamp scrollback box widths + suppress status bar after resize (#25975) 2026-05-14 15:22:44 -07:00
test_cli_goal_interrupt.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_cli_image_command.py fix(termux): harden execute_code and mobile browser/audio UX 2026-04-09 16:24:53 -07:00
test_cli_init.py fix: preserve Ctrl+J newlines in Ghostty 2026-05-28 23:30:39 -07:00
test_cli_insights_command.py fix(cli): parse positional insights days 2026-05-12 14:56:47 -07:00
test_cli_interrupt_subagent.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_cli_light_mode.py fix(cli): stop OSC 11 bg probe from trapping users in a stray editor (#35441) 2026-05-30 11:55:12 -05:00
test_cli_loading_indicator.py fix: clean up defensive shims and finish CI stabilization from #17660 (#17801) 2026-04-29 23:53:17 -07:00
test_cli_markdown_rendering.py fix(cli): preserve cron asterisks in strip mode 2026-05-18 20:08:36 -07:00
test_cli_mcp_config_watch.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_cli_new_session.py fix(cli): synchronize HERMES_SESSION_ID across environment and contextvar during session switches 2026-05-23 17:46:55 -07:00
test_cli_prefix_matching.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_cli_preloaded_skills.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_cli_provider_resolution.py feat(tools): surface the free tool pool in entitlement + setup (#36153) 2026-06-01 06:32:48 +05:30
test_cli_reload_skills.py refactor(reload-skills): queue note for next turn, drop cache invalidation + agent tool 2026-04-29 21:07:47 -07:00
test_cli_resume_command.py fix(cli): use Rich [dim] tag instead of ANSI escape in _restore_session_cwd 2026-06-05 10:00:21 +08:00
test_cli_retry.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00
test_cli_save_config_value.py fix(cli): preserve config comments on setting writes 2026-05-09 17:55:12 -07:00
test_cli_secret_capture.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
test_cli_shift_enter_newline.py feat(cli): recognise Shift+Enter as a newline key 2026-05-08 16:26:51 -07:00
test_cli_shutdown_memory_messages.py fix(cli): pass session messages to shutdown_memory_provider (#15165 sibling) 2026-04-27 06:41:16 -07:00
test_cli_skin_integration.py fix(tui): restore macOS copy behavior and theme polish (#17131) 2026-04-28 18:47:14 -05:00
test_cli_status_bar.py fix(cli): clamp post-compression token sentinel in status bar (#35858) 2026-05-31 06:03:01 -07:00
test_cli_status_command.py fix(profile): use existing get_active_profile_name() for /profile command 2026-04-15 17:52:03 -07:00
test_cli_steer_busy_path.py fix(cli): dispatch /steer inline while agent is running (#13354) 2026-04-20 23:05:38 -07:00
test_cli_terminal_response_sanitizer.py fix(cli): tighten mouse leak sanitizer 2026-04-29 22:10:18 -05:00
test_cli_terminal_shortcuts.py fix(cli): ignore terminal focus reports (salvage of #16780) 2026-05-29 00:31:44 -07:00
test_cli_tools_command.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_cli_user_message_preview.py feat(cli): improve multiline previews 2026-04-20 02:53:40 -07:00
test_cli_yolo_toggle.py fix(cli): /yolo in chat must enable session bypass, not just set env var 2026-05-28 12:10:21 -07:00
test_compress_focus.py feat: /compress <focus> — guided compression with focus topic (#8017) 2026-04-11 19:23:29 -07:00
test_compress_here.py Inspired by Claude Code: /compress here [N] — boundary-aware 'summarize up to here' (#35048) 2026-05-29 17:49:15 -07:00
test_cprint_bg_thread.py fix: preserve ansi output history on resize replay 2026-05-14 15:14:29 -07:00
test_ctrl_enter_newline.py fix: preserve Ctrl+J newlines in Ghostty 2026-05-28 23:30:39 -07:00
test_cwd_env_respect.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_destructive_slash_confirm.py fix(cli): add inline --yes/now skip for destructive slash commands (#30768) 2026-05-24 16:13:03 -07:00
test_destructive_slash_inline_skip_e2e.py fix(cli): add inline --yes/now skip for destructive slash commands (#30768) 2026-05-24 16:13:03 -07:00
test_exit_delete_session.py feat(cli): add /exit --delete flag to remove session on quit (#27101) 2026-05-16 12:51:08 -07:00
test_exit_summary_resume_hint.py test(cli): cover exit resume hint -p flag across profiles 2026-05-25 01:41:54 -07:00
test_fast_command.py feat(desktop): inline model picker in the status bar 2026-06-02 19:09:41 -05:00
test_gquota_command.py fix(cli): sanitize interactive command output 2026-04-19 01:16:34 -07:00
test_manual_compress.py fix(cli): persist manual compress handoff 2026-05-05 04:42:48 -07:00
test_partial_compress.py Inspired by Claude Code: /compress here [N] — boundary-aware 'summarize up to here' (#35048) 2026-05-29 17:49:15 -07:00
test_personality_none.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_prefill_config.py fix(config): align prefill messages key handling 2026-06-03 23:51:44 -06:00
test_prepend_note_to_message.py refactor(cli): normalize note and avoid blank lines in prepend helper 2026-06-01 20:30:08 -07:00
test_prompt_text_input_thread_safety.py fix: use TUI modal for slash confirmations 2026-05-11 10:02:03 -07:00
test_quick_commands.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_reasoning_command.py chore: ruff auto-fix PLR6201 resweep — tuple → set in membership tests (#27355) 2026-05-17 02:29:41 -07:00
test_resume_display.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_resume_quiet_stderr.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_save_conversation_location.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_session_boundary_hooks.py feat(observability): observer-grade telemetry hooks + NeMo-Relay plugin 2026-06-03 06:36:46 -07:00
test_slash_command_interrupt.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_slash_confirm_windows.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_steer_inline_repaint_34569.py fix(cli): repaint input area after inline /steer and /model submit (#34839) 2026-05-29 19:04:40 -07:00
test_stream_delta_think_tag.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_surrogate_sanitization.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_tool_progress_scrollback.py fix(cli): decouple tool_progress=verbose from global DEBUG logging (#31379) 2026-05-24 02:19:20 -07:00
test_tui_terminal_reset_on_exit.py fix(cli): reset terminal input modes on TUI exit to stop focus/mouse leaks 2026-06-01 23:27:44 +08:00
test_update_command.py feat(cli): add /update slash command to CLI and TUI (#23854) 2026-05-18 20:10:46 -04:00
test_version_command.py Add /version slash command across CLI, gateway, TUI, and desktop. 2026-06-05 18:05:05 -07:00
test_worktree.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_worktree_security.py refactor(tests): re-architect tests + fix CI failures (#5946) 2026-04-07 17:19:07 -07:00