hermes - 💡(How to fix) Fix [Bug]: Ctrl+C in `hermes model` falls back to numbered list instead of exiting

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

The issue is in the fallback chain between curses_radiolist() and _prompt_provider_choice():

hermes_cli/curses_ui.py line 283-284:

except KeyboardInterrupt:
    return cancel_returns  # returns -1, does NOT exit

curses_radiolist catches KeyboardInterrupt and returns cancel_returns (which is -1). Back in _prompt_provider_choice() (hermes_cli/main.py ~line 2462), the check if idx >= 0 fails for -1, so it falls through to the numbered fallback list (line 2479+) instead of exiting.

The fallback numbered list in _prompt_provider_choice (line 2473) catches KeyboardInterrupt and calls sys.exit(1) — that is what finally exits on the second Ctrl+C.

The same pattern exists in the model selection layer (_prompt_model_selection in hermes_cli/auth.py ~line 4956), where simple_term_menu catches Ctrl+C and returns None, falling back to a numbered list.

Code Example

except KeyboardInterrupt:
    return cancel_returns  # returns -1, does NOT exit

---

except KeyboardInterrupt:
    raise  # propagate instead of returning cancel_returns

---

idx = _curses_prompt_choice(\"Select provider:\", choices, default)
if idx is None:
    return None
# idx == -1 means user cancelled via ESC/q (not Ctrl+C)
if idx >= 0:
    print()
    return idx
# ESC/q on the radiolist falls through to numbered fallback — that is fine.
# But Ctrl+C should not reach here — it should be propagated.
RAW_BUFFERClick to expand / collapse

Bug Description

hermes model shows a curses-based arrow-key provider picker. Pressing Ctrl+C does not exit the command — instead it closes the curses UI and falls back to a plain-text numbered list of the same providers. A second Ctrl+C is required to actually exit.

The expected behavior is that Ctrl+C exits the command immediately.

Steps to Reproduce

  1. Run hermes model in a terminal
  2. The curses arrow-key provider picker appears
  3. Press Ctrl+C
  4. A numbered text list of providers appears (same choices, different UI)
  5. Press Ctrl+C again
  6. Command exits

Expected Behavior

Ctrl+C should exit hermes model immediately, regardless of which UI layer is active.

Actual Behavior

First Ctrl+C closes the curses radiolist and falls through to a numbered fallback list. Second Ctrl+C exits. This is confusing because the user sees an "other interface" appear rather than the expected exit.

Root Cause Analysis

The issue is in the fallback chain between curses_radiolist() and _prompt_provider_choice():

hermes_cli/curses_ui.py line 283-284:

except KeyboardInterrupt:
    return cancel_returns  # returns -1, does NOT exit

curses_radiolist catches KeyboardInterrupt and returns cancel_returns (which is -1). Back in _prompt_provider_choice() (hermes_cli/main.py ~line 2462), the check if idx >= 0 fails for -1, so it falls through to the numbered fallback list (line 2479+) instead of exiting.

The fallback numbered list in _prompt_provider_choice (line 2473) catches KeyboardInterrupt and calls sys.exit(1) — that is what finally exits on the second Ctrl+C.

The same pattern exists in the model selection layer (_prompt_model_selection in hermes_cli/auth.py ~line 4956), where simple_term_menu catches Ctrl+C and returns None, falling back to a numbered list.

Proposed Fix

KeyboardInterrupt should propagate out of curses_radiolist() rather than being caught and converted to a cancel return value. Alternatively, _prompt_provider_choice should re-raise KeyboardInterrupt when idx == -1 (the cancel_returns value) to let it bubble up and exit.

Minimal fix in curses_ui.py:

except KeyboardInterrupt:
    raise  # propagate instead of returning cancel_returns

Or in _prompt_provider_choice():

idx = _curses_prompt_choice(\"Select provider:\", choices, default)
if idx is None:
    return None
# idx == -1 means user cancelled via ESC/q (not Ctrl+C)
if idx >= 0:
    print()
    return idx
# ESC/q on the radiolist falls through to numbered fallback — that is fine.
# But Ctrl+C should not reach here — it should be propagated.

Environment

  • macOS 26.4.1 (arm64)
  • Python 3.11.15
  • Hermes Agent v0.14.0 (2026.5.16)

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 [Bug]: Ctrl+C in `hermes model` falls back to numbered list instead of exiting