hermes - 💡(How to fix) Fix /reload-mcp confirmation prompt is broken — async coroutine never awaited

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

In cli.py, the method _prompt_text_input() (around line 5481) calls run_in_terminal(_ask) directly:

# cli.py ~line 5497 (before fix)
if self._app:
    from prompt_toolkit.application import run_in_terminal
    ...
    run_in_terminal(_ask)

run_in_terminal is an async function — it returns a coroutine object. This call happens inside the process_loop background thread (a regular threading.Thread), not on the event loop thread. The coroutine is never awaited, so _ask never executes, result[0] stays None, and the method returns None immediately.

For comparison, the sibling method _run_curses_picker() (around line 5451) has a correct thread guard:

in_main_thread = threading.current_thread() is threading.main_thread()
if self._app and in_main_thread:
    run_in_terminal(_pick)  # same bug here, but never reached from process_loop
else:
    _pick()

Code Example

RuntimeWarning: coroutine 'run_in_terminal.<locals>.run' was never awaited

---

# cli.py ~line 5497 (before fix)
if self._app:
    from prompt_toolkit.application import run_in_terminal
    ...
    run_in_terminal(_ask)

---

in_main_thread = threading.current_thread() is threading.main_thread()
if self._app and in_main_thread:
    run_in_terminal(_pick)  # same bug here, but never reached from process_loop
else:
    _pick()

---

def _prompt_text_input(self, prompt_text: str) -> str | None:
    import asyncio
    import threading

    result = [None]

    def _ask():
        try:
            result[0] = input(prompt_text).strip() or None
        except (KeyboardInterrupt, EOFError):
            pass

    if self._app:
        from prompt_toolkit.application import run_in_terminal
        was_visible = self._status_bar_visible
        self._status_bar_visible = False
        self._app.invalidate()
        try:
            in_main_thread = threading.current_thread() is threading.main_thread()
            if in_main_thread:
                _ask()
            else:
                future = asyncio.run_coroutine_threadsafe(
                    run_in_terminal(_ask), self._app.loop
                )
                future.result()
        finally:
            self._status_bar_visible = was_visible
            self._app.invalidate()
    else:
        _ask()
    return result[0]
RAW_BUFFERClick to expand / collapse

What happened

Typing /reload-mcp in the CLI should show an interactive confirmation dialog with three choices (Approve Once / Always Approve / Cancel). Instead, the prompt is skipped entirely and the user is dropped directly back to the chat input — the reload never happens.

The Python runtime also emits a RuntimeWarning:

RuntimeWarning: coroutine 'run_in_terminal.<locals>.run' was never awaited

Root cause

In cli.py, the method _prompt_text_input() (around line 5481) calls run_in_terminal(_ask) directly:

# cli.py ~line 5497 (before fix)
if self._app:
    from prompt_toolkit.application import run_in_terminal
    ...
    run_in_terminal(_ask)

run_in_terminal is an async function — it returns a coroutine object. This call happens inside the process_loop background thread (a regular threading.Thread), not on the event loop thread. The coroutine is never awaited, so _ask never executes, result[0] stays None, and the method returns None immediately.

For comparison, the sibling method _run_curses_picker() (around line 5451) has a correct thread guard:

in_main_thread = threading.current_thread() is threading.main_thread()
if self._app and in_main_thread:
    run_in_terminal(_pick)  # same bug here, but never reached from process_loop
else:
    _pick()

Suggested fix

Use asyncio.run_coroutine_threadsafe() to schedule the coroutine on the app's event loop when called from a background thread:

def _prompt_text_input(self, prompt_text: str) -> str | None:
    import asyncio
    import threading

    result = [None]

    def _ask():
        try:
            result[0] = input(prompt_text).strip() or None
        except (KeyboardInterrupt, EOFError):
            pass

    if self._app:
        from prompt_toolkit.application import run_in_terminal
        was_visible = self._status_bar_visible
        self._status_bar_visible = False
        self._app.invalidate()
        try:
            in_main_thread = threading.current_thread() is threading.main_thread()
            if in_main_thread:
                _ask()
            else:
                future = asyncio.run_coroutine_threadsafe(
                    run_in_terminal(_ask), self._app.loop
                )
                future.result()
        finally:
            self._status_bar_visible = was_visible
            self._app.invalidate()
    else:
        _ask()
    return result[0]

(Similarly, _run_curses_picker's run_in_terminal(_pick) call at line 5472 has the same missing-await issue, though it's currently unreachable from the background thread path.)

Environment

  • Hermes Agent version: latest (main branch)
  • Python: 3.12+
  • prompt_toolkit: 3.x
  • OS: macOS

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix /reload-mcp confirmation prompt is broken — async coroutine never awaited