NousResearch/hermes-agent
Skills install command soft-locks TUI via blocking prompt
Cluster 3474 · hermes cli. Data as of
Nothing open on GitHub. Fix merged in #3586 on 28 Mar 2026. Check it covers your case.
The short answer
0open issues
0open PRs
3items grouped
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #3586. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #3586. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A bug where the /skills install (and uninstall) commands soft-lock the TUI because of a blocking input() confirmation call. The fix skips the confirmation step in slash command contexts.
Open issues0
Open PRs0
Fixes on record
-
#3586 fix(skills): skip confirmation in /skills install and uninstall slash commands
files:
hermes_cli/skills_hub.py,tests/hermes_cli/test_skills_skip_confirm.pyShow the diff
-
hermes_cli/skills_hub.py@@ -304,7 +304,8 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all", def do_install(identifier: str, category: str = "", force: bool = False, - console: Optional[Console] = None, skip_confirm: bool = False) -> None: + console: Optional[Console] = None, skip_confirm: bool = False, + invalidate_cache: bool = True) -> None: """Fetch, quarantine, scan, confirm, and install a skill.""" from tools.skills_hub import ( GitHubAuth, create_source_router, ensure_hub_dirs, @@ -417,12 +418,16 @@ def do_install(identifier: str, category: str = "", force: bool = False, c.print(f"[bold green]Installed:[/] {install_dir.relative_to(SKILLS_DIR)}") c.print(f"[dim]Files: {', '.join(bundle.files.keys())}[/]\n") - # Invalidate the skills prompt cache so the new skill appears immediately - try: - from agent.prompt_builder import clear_skills_system_prompt_cache - clear_skills_system_prompt_cache(clear_snapshot=True) - except Exception: - pass + if invalidate_cache: + # Invalidate the skills prompt cache so the new skill appears immediately + try: + from agent.prompt_builder import clear_skills_system_prompt_cache + clear_skills_system_prompt_cache(clear_snapshot=True) + except Exception: + pass + else: + c.print("[dim]Skill will be available in your next session.[/]") + c.print("[dim]Use /reset to start a new session now, or --now to activate immediately (invalidates prompt cache).[/]\n") def do_inspect(identifier: str, console: Optional[Console] = None) -> None: @@ -610,7 +615,8 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> N def do_uninstall(name: str, console: Optional[Console] = None, - skip_confirm: bool = False) -> None: + skip_confirm: bool = False, + invalidate_cache: bool = True) -> None: """Remove a hub-installed skill with confirmation.""" from tools.skills_hub import uninstall_skill @@ -630,11 +636,15 @@ def do_uninstall(name: str, console: Optional[Console] = None, success, msg = uninstall_skill(name) if success: c.print(f"[bold green]{msg}[/]\n") - try: - from agent.prompt_builder import clear_skills_system_prompt_cache - clear_skills_system_prompt_cache(clear_snapshot=True) - except Exception: - pass + if invalidate_cache: + try: + from agent.prompt_builder import clear_skills_system_prompt_cache + clear_skills_system_prompt_cache(clear_snapshot=True) + except Exception: + pass + else: + c.print("[dim]Change will take effect in your next session.[/]") + c.print("[dim]Use /reset to start a new session now, or --now to apply immediately (invalidates prompt cache).[/]\n") else: c.print(f"[bold red]Error:[/] {msg}\n") @@ -1071,19 +1081,23 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None: elif action == "install": if not args: - c.print("[bold red]Usage:[/] /skills install <identifier> [--category <cat>] [--force|--yes]\n") + c.print("[bold red]Usage:[/] /skills install <identifier> [--category <cat>] [--force] [--now]\n") return identifier = args[0] category = "" - # --yes / -y bypasses confirmation prompt (needed in TUI mode) - # --force handles reinstall override - skip_confirm = any(flag in args for flag in ("--yes", "-y")) + # Slash commands run inside prompt_toolkit where input() hangs. + # Always skip confirmation — the user typing the command is implicit consent. + skip_confirm = True force = "--force" in args + # --now invalidates prompt cache immediately (costs more money). + # Default: defer to next session to preserve cache. + invalidate_cache = "--now" in args for i, a in enumerate(args): if a == "--category" and i + 1 < len(args): category = args[i + 1] do_install(identifier, category=category, force=force, - skip_confirm=skip_confirm, console=c) + skip_confirm=skip_confirm, invalidate_cache=invalidate_cache, + console=c) elif action == "inspect": if not args: @@ -1113,10 +1127,13 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None: elif action == "uninstall": if not args: - c.print("[bold red]Usage:[/] /skills uninstall <name> [--yes]\n") + c.print("[bold red]Usage:[/] /skills uninstall <name> [--now]\n") return - skip_confirm = any(flag in args for flag in ("--yes", "-y")) - do_uninstall(args[0], console=c, skip_confirm=skip_confirm) + # Slash commands run inside prompt_toolkit where input() hangs. + skip_confirm = True + invalidate_cache = "--now" in args + do_uninstall(args[0], console=c, skip_confirm=skip_confirm, + invalidate_cache=invalidate_cache) elif action == "publish": if not args: -
tests/hermes_cli/test_skills_skip_confirm.py@@ -1,10 +1,13 @@ """ -Tests for skip_confirm behavior in /skills install and /skills uninstall. +Tests for skip_confirm and invalidate_cache behavior in /skills install +and /skills uninstall slash commands. -Verifies that --yes / -y bypasses the interactive confirmation prompt -that hangs inside prompt_toolkit's TUI. +Slash commands always skip confirmation (input() hangs in TUI). +Cache invalidation is deferred by default; --now opts into immediate +invalidation (at the cost of breaking prompt cache mid-session). Based on PR #1595 by 333Alden333 (salvaged). +Updated for PR #3586 (cache-aware install/uninstall). """ from unittest.mock import patch, MagicMock @@ -32,23 +35,43 @@ def test_y_flag_sets_skip_confirm(self): _, kwargs = mock_install.call_args assert kwargs.get("skip_confirm") is True - def test_force_flag_sets_force_not_skip(self): + def test_force_flag_sets_force(self): from hermes_cli.skills_hub import handle_skills_slash with patch("hermes_cli.skills_hub.do_install") as mock_install: handle_skills_slash("/skills install test/skill --force") mock_install.assert_called_once() _, kwargs = mock_install.call_args assert kwargs.get("force") is True - assert kwargs.get("skip_confirm") is False + # Slash commands always skip confirmation (input() hangs in TUI) + assert kwargs.get("skip_confirm") is True - def test_no_flags(self): + def test_no_flags_still_skips_confirm(self): + """Slash commands always skip confirmation — input() hangs in TUI.""" from hermes_cli.skills_hub import handle_skills_slash with patch("hermes_cli.skills_hub.do_install") as mock_install: handle_skills_slash("/skills install test/skill") mock_install.assert_called_once() _, kwargs = mock_install.call_args assert kwargs.get("force") is False - assert kwargs.get("skip_confirm") is False + assert kwargs.get("skip_confirm") is True + + def test_default_defers_cache_invalidation(self): + """Without --now, cache invalidation is deferred to next session.""" + from hermes_cli.skills_hub import handle_skills_slash + with patch("hermes_cli.skills_hub.do_install") as mock_install: + handle_skills_slash("/skills install test/skill") + mock_install.assert_called_once() + _, kwargs = mock_install.call_args + assert kwargs.get("invalidate_cache") is False + + def test_now_flag_invalidates_cache(self): + """--now opts into immediate cache invalidation.""" + from hermes_cli.skills_hub import handle_skills_slash + with patch("hermes_cli.skills_hub.do_install") as mock_install: + handle_skills_slash("/skills install test/skill --now") + mock_install.assert_called_once() + _, kwargs = mock_install.call_args + assert kwargs.get("invalidate_cache") is True class TestHandleSkillsSlashUninstallFlags: @@ -70,13 +93,32 @@ def test_y_flag_sets_skip_confirm(self): _, kwargs = mock_uninstall.call_args assert kwargs.get("skip_confirm") is True - def test_no_flags(self): + def test_no_flags_still_skips_confirm(self): + """Slash commands always skip confirmation — input() hangs in TUI.""" from hermes_cli.skills_hub import handle_skills_slash with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: handle_skills_slash("/skills uninstall test-skill") mock_uninstall.assert_called_once() _, kwargs = mock_uninstall.call_args - assert kwargs.get("skip_confirm", False) is False + assert kwargs.get("skip_confirm") is True + + def test_default_defers_cache_invalidation(self): + """Without --now, cache invalidation is deferred to next session.""" + from hermes_cli.skills_hub import handle_skills_slash + with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: + handle_skills_slash("/skills uninstall test-skill") + mock_uninstall.assert_called_once() + _, kwargs = mock_uninstall.call_args + assert kwargs.get("invalidate_cache") is False + + def test_now_flag_invalidates_cache(self): + """--now opts into immediate cache invalidation.""" + from hermes_cli.skills_hub import handle_skills_slash + with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: + handle_skills_slash("/skills uninstall test-skill --now") + mock_uninstall.assert_called_once() + _, kwargs = mock_uninstall.call_args + assert kwargs.get("invalidate_cache") is True class TestDoInstallSkipConfirm:
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#3496 fix(skills): /skills install soft-locks in TUI due to blocking input() call · merged via another change →
#3586
Closed issues · 1
- Closed#3474[Bug]: Soft-locking when installing obsidian-cli skill
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
- #3586
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 28 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.