# NousResearch/hermes-agent · Ctrl+Shift+C keybinding crash and clipboard conflicts

## Open now

- 2 issues, 2 PRs open on GitHub.
- #4256: \[UX\] Support configurable keybindings via config.yaml
- #21564: /help command text incorrect
- PR #23128: fix(tui): clarify copy shortcut help text
- PR #51830: fix(tui): correct macOS hotkey help text to reflect actual TUI behavior

A 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.

## TL;DR

- 18 member(s): 2 open issue(s), 6 closed issue(s), 2 open PR(s), 3 merged PR(s), 5 PR(s) closed without merging.
- A fix was merged to main in PR #19884. Verify it addresses your case.
- **Recommended:** read PR #19884 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-19895`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #4256: \[UX\] Support configurable keybindings via config.yaml
- #21564: /help command text incorrect

## Closed / duplicate issues

- #16181: \[Bug\]: Ctrl+C Copy/Interrupt Conflict in CLI
- #19894: macOS: hermes crashes at startup — Invalid key c-S-c (Ctrl+Shift+C)
- #19896: \[Bug\]: Linux: hermes crashes at startup — Invalid key c-S-c
- #19903: CLI crash on startup: Invalid key 'c-S-c' — prompt_toolkit doesn't support Shift modifier
- #20202: cli.py crashes on startup in WSL terminals that don't support c-S-c keybinding
- #46448: feat(tui): pass Ctrl+C to VSCode terminal for native clipboard copy

## Prior & competing PRs

- **#19884: fix(cli): resolve Ctrl+C copy/interrupt conflict with Ctrl+Shift+C + Ctrl+Q aliases**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`, `hermes_cli/config.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -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):
    ```
- **#19907: fix(cli): remove invalid ctrl-shift-c keybinding**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#19894`, `#19902`, `#19906`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#19919: refactor(cli): drop dead c-S-c key binding (follow-up to #19895)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -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):
    ```
- **#16183: fix(cli): fix shortcut config conflict in hermes_cli**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#19884`
- **#19912: fix(cli): remove invalid 'c-S-c' keybinding that crashes prompt_toolkit**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#19919`
- **#21753: fix(tui): avoid unsupported Cmd hotkeys in macOS help**. closed without merge
  - maintainer direction: category `stale_abandoned`; implication `active`
- **#23128: fix(tui): clarify copy shortcut help text**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `ui-tui/src/__tests__/constants.test.ts`, `ui-tui/src/components/prompts.tsx`, `ui-tui/src/content/hotkeys.ts`
- **#26318: fix(tui): document portable macOS help shortcuts**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#21753`, `#23128`, `#21564`
- **#51830: fix(tui): correct macOS hotkey help text to reflect actual TUI behavior**. open
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#21564`, `#21753`, `#26318`
  - files: `ui-tui/src/content/hotkeys.ts`

## Fix status

- Merged to main in PR #19884 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-04`.
- Subsystem: `docs`
