Ctrl+Shift+C keybinding crash and clipboard conflicts
NousResearch/hermes-agent · cluster 4256 · docs
Data as of
4 open items. Fix merged in #19884. Check it covers your case.
The short answer
- Known?
- Yes. 18 related items grouped here, 6 issues already closed.
- Fix event
- A fix was merged to main in PR #19884. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #19895. That is separate from the fix event. - Still open
- 2 issues and 2 PRs.
Next step: read #19884, then check whether your report is one of the 2 still open.
Read #19884Pick up a Lab taskA cluster of bugs, fixes, and related work around an invalid 'c-S-c' (Ctrl+Shift+C) keybinding that crashes prompt_toolkit on startup across platforms, plus adjacent issues with Ctrl+C copy/interrupt conflicts, macOS keybinding parity, and help text accuracy.
Open issues2
Open PRs2
Fixes on record
-
#19884 fix(cli): resolve Ctrl+C copy/interrupt conflict with Ctrl+Shift+C + Ctrl+Q aliases
files:
cli.py,hermes_cli/config.pyShow the diff
-
cli.py@@ -10483,7 +10483,98 @@ def handle_ctrl_c(event): else: self._should_exit = True event.app.exit() - + + @kb.add('c-S-c') # Ctrl+Shift+C + def handle_ctrl_shift_c(event): + """Copy text to clipboard (terminal-native). + + This is a no-op at the application level. Terminal emulators + handle the actual copy operation when Ctrl+Shift+C is pressed. + This binding prevents Hermes from intercepting the keystroke + as an interrupt signal. + + On macOS the standard copy shortcut is Cmd+C (no Hermes binding + needed). On Linux/Windows Ctrl+Shift+C is the conventional + terminal copy shortcut. + """ + return # No-op — let the terminal perform native copy + + @kb.add('c-q') # Ctrl+Q + def handle_ctrl_q(event): + """Alternative interrupt/exit shortcut (Ctrl+Q). + + Behaves like Ctrl+C: cancels active prompts, interrupts the + running agent, or clears the input buffer. Does not support + the double-press 'force exit' feature of Ctrl+C. + """ + # Cancel active voice recording. + _should_cancel_voice = False + _recorder_ref = None + with cli_ref._voice_lock: + if cli_ref._voice_recording and cli_ref._voice_recorder: + _recorder_ref = cli_ref._voice_recorder + cli_ref._voice_recording = False + cli_ref._voice_continuous = False + _should_cancel_voice = True + if _should_cancel_voice: + _cprint(f"\n{_DIM}Recording cancelled.{_RST}") + threading.Thread( + target=_recorder_ref.cancel, daemon=True + ).start() + event.app.invalidate() + return + + # Cancel sudo prompt + if self._sudo_state: + self._sudo_state["response_queue"].put("") + self._sudo_state = None + event.app.invalidate() + return + + # Cancel secret prompt + if self._secret_state: + self._cancel_secret_capture() + event.app.current_buffer.reset() + event.app.invalidate() + return + + # Cancel approval prompt (deny) + if self._approval_state: + self._approval_state["response_queue"].put("deny") + self._approval_state = None + event.app.invalidate() + return + + # Cancel /model picker + if self._model_picker_state: + self._close_model_picker() + event.app.current_buffer.reset() + event.app.invalidate() + return + + # Cancel clarify prompt + if self._clarify_state: + self._clarify_state["response_queue"].put( + "The user cancelled. Use your best judgement to proceed." + ) + self._clarify_state = None + self._clarify_freetext = False + event.app.current_buffer.reset() + event.app.invalidate() + return + + if self._agent_running and self.agent: + print("\n⚡ Interrupting agent...") + self.agent.interrupt() + else: + if event.app.current_buffer.text or self._attached_images: + event.app.current_buffer.reset() + self._attached_images.clear() + event.app.invalidate() + else: + self._should_exit = True + event.app.exit() + @kb.add('c-d') def handle_ctrl_d(event): """Ctrl+D: delete char under cursor (standard readline behaviour). -
hermes_cli/config.py@@ -809,6 +809,7 @@ def _ensure_hermes_home_managed(home: Path): "enabled": False, "fields": ["model", "context_pct", "cwd"], # Order shown; drop any to hide }, + "copy_shortcut": "auto", # "auto" (platform default) | "ctrl_c" | "ctrl_shift_c" | "disabled" }, # Web dashboard settings
-
-
#19895 fix(cli): guard c-S-c key binding with try/except to prevent startup crash
files:
cli.pyShow the diff
-
cli.py@@ -10484,20 +10484,19 @@ def handle_ctrl_c(event): self._should_exit = True event.app.exit() - @kb.add('c-S-c') # Ctrl+Shift+C - def handle_ctrl_shift_c(event): - """Copy text to clipboard (terminal-native). - - This is a no-op at the application level. Terminal emulators - handle the actual copy operation when Ctrl+Shift+C is pressed. - This binding prevents Hermes from intercepting the keystroke - as an interrupt signal. - - On macOS the standard copy shortcut is Cmd+C (no Hermes binding - needed). On Linux/Windows Ctrl+Shift+C is the conventional - terminal copy shortcut. - """ - return # No-op — let the terminal perform native copy + try: + @kb.add('c-S-c') # Ctrl+Shift+C — no-op, let terminal handle native copy + def handle_ctrl_shift_c(event): + """No-op: let the terminal handle Ctrl+Shift+C natively. + + Wrapped in try/except because prompt_toolkit raises ValueError + ("Invalid key: c-S-c") on platforms where this key spec is not + recognised. The binding is best-effort; if registration fails, + startup continues normally. + """ + return + except ValueError: + pass # prompt_toolkit on this platform/version doesn't support c-S-c @kb.add('c-q') # Ctrl+Q def handle_ctrl_q(event):
-
-
#19919 refactor(cli): drop dead c-S-c key binding (follow-up to #19895)
files:
cli.pyShow the diff
-
cli.py@@ -10484,19 +10484,14 @@ def handle_ctrl_c(event): self._should_exit = True event.app.exit() - try: - @kb.add('c-S-c') # Ctrl+Shift+C — no-op, let terminal handle native copy - def handle_ctrl_shift_c(event): - """No-op: let the terminal handle Ctrl+Shift+C natively. - - Wrapped in try/except because prompt_toolkit raises ValueError - ("Invalid key: c-S-c") on platforms where this key spec is not - recognised. The binding is best-effort; if registration fails, - startup continues normally. - """ - return - except ValueError: - pass # prompt_toolkit on this platform/version doesn't support c-S-c + # Ctrl+Shift+C: no binding needed. Terminal emulators (GNOME Terminal, + # iTerm2, kitty, Windows Terminal, etc.) intercept Ctrl+Shift+C before + # the keystroke reaches the application's stdin — prompt_toolkit never + # sees it, and prompt_toolkit's key spec parser doesn't even recognise + # 'c-S-c' anyway (the Shift modifier is meaningless on control-sequence + # keys). #19884 added a handler for this; #19895 patched the resulting + # startup crash with try/except. Both were based on a misreading of how + # terminal key events propagate. Deleting the dead handler outright. @kb.add('c-q') # Ctrl+Q def handle_ctrl_q(event):
-
5 PRs closed without merging
From the close comments: done elsewhere 1 · merged via another change 1 · stale or abandoned 1 · superseded by another PR 1 · other 1
-
#16183 fix(cli): fix shortcut config conflict in hermes_cli · merged via another change →
#19884 -
#19907 fix(cli): remove invalid ctrl-shift-c keybinding · superseded by another PR →
#19894,#19902,#19906 -
#19912 fix(cli): remove invalid 'c-S-c' keybinding that crashes prompt_toolkit · done elsewhere →
#19919 - #21753 fix(tui): avoid unsupported Cmd hotkeys in macOS help · stale or abandoned
-
#26318 fix(tui): document portable macOS help shortcuts · other →
#21753,#23128,#21564
Closed issues · 6
- Closed#16181[Bug]: Ctrl+C Copy/Interrupt Conflict in CLI
- Closed#19894macOS: hermes crashes at startup — Invalid key c-S-c (Ctrl+Shift+C)
- Closed#19896[Bug]: Linux: hermes crashes at startup — Invalid key c-S-c
- Closed#19903CLI crash on startup: Invalid key 'c-S-c' — prompt_toolkit doesn't support Shift modifier
- Closed#20202cli.py crashes on startup in WSL terminals that don't support c-S-c keybinding
- Closed#46448feat(tui): pass Ctrl+C to VSCode terminal for native clipboard copy
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.12: 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 Lab