fix(clipboard): salvage #16020 with native/tmux success signal + xterm keybinding#16035
Merged
Conversation
…etection
Problem: Ctrl+C in Hermes TUI shows 'copied' but clipboard often empty.
Root causes:
- Native Linux tools (xclip, wl-copy) require DISPLAY/WAYLAND_DISPLAY; in
headless Docker/SSH they fail or hang.
- OSC 52 fallback requires terminal emulator support; when absent, sequence
is dropped silently.
- Dashboard OSC 52 → Clipboard API path fails due to missing user gesture;
errors were silently caught.
- User feedback 'copied selection' was shown unconditionally, regardless of
success.
Solution implemented:
- Short-circuit Linux native clipboard probing when no display server is
present (no DISPLAY and no WAYLAND_DISPLAY). Avoids futile attempts and
timeouts.
- Add HERMES_TUI_DEBUG_CLIPBOARD env var (1/true). When set, TUI logs to
stderr which clipboard path is used, probe results on Linux, and whether
OSC 52 was emitted. Greatly improves diagnosability.
- Improve dashboard clipboard error handling: replace empty catch blocks
with console.warn messages for OSC 52 decode/Write failures and direct
copy/paste errors. Makes browser permission/user-gesture failures visible
in DevTools.
- Add comprehensive clipboard troubleshooting documentation to README and
AGENTS, covering OSC 52 verification, tmux config, Docker/headless
constraints, env vars, dashboard caveats, and fallback strategies.
Technical details:
- in ui-tui/packages/hermes-ink/src/ink/termio/osc.ts:
- Early return on Linux if both DISPLAY and WAYLAND_DISPLAY unset.
- Refactor probe sequence to async with 500ms timeout,
caching result; subsequent copies use cached tool immediately.
- Emit debug logs when HERMES_TUI_DEBUG_CLIPBOARD=1.
- in ink.tsx: log when OSC 52 not emitted (native
or tmux path in use) in debug mode.
- : OSC 52 handler and Ctrl+Shift+C handler now
log warnings to console on Clipboard API rejection with error message.
- Documentation: new 'Clipboard Troubleshooting' section in README; new
'Clipboard environment variables and pitfalls' subsection in AGENTS.md
(Known Pitfalls).
Tests: full ui-tui test suite (292 tests) passes; clipboard and OSC tests
unaffected. No breaking changes.
Files changed:
- ui-tui/packages/hermes-ink/src/ink/termio/osc.ts
- ui-tui/packages/hermes-ink/src/ink/ink.tsx
- web/src/pages/ChatPage.tsx
- README.md
- AGENTS.md
- CHANGELOG.md (new)
…RMES_TUI_FORCE_OSC52 - Dashboard copy: direct Clipboard API on Ctrl+C/Cmd+C (user gesture); send Escape to TUI to clear selection; Ctrl+Shift+C kept as fallback. - TUI /copy: copySelection() async; only reports success if OSC52 emitted. - Add HERMES_TUI_FORCE_OSC52 env var to override native-tool detection. - Fixes "copied N chars" false-positive when clipboard backend absent. Changes: web/src/pages/ChatPage.tsx — direct navigator.clipboard.writeText ui-tui/packages/hermes-ink/src/ink/ink.tsx — async copySelection ui-tui/packages/hermes-ink/src/ink/termio/osc.ts — HERMES_TUI_FORCE_OSC52 ui-tui/src/app/slash/commands/core.ts — async /copy with honest feedback
…board Follow-up on #16020 salvage. Three corrections: 1. Truth signal for /copy Before: success was 'OSC 52 sequence was emitted to stdout'. That's false on local Linux inside tmux (emitSequence=false), so /copy kept printing 'clipboard copy failed' to users whose xclip/wl-copy had already succeeded fire-and-forget. Fix: setClipboard() now returns { sequence, success } where success = native-fired OR tmux-buffer-loaded OR osc52-emitted. copyNative() returns a boolean telling setClipboard whether a native attempt was made. /copy only shows 'failed' when literally no path was taken. 2. Dashboard keybinding Before: Ctrl+C for copy on non-Mac (Ctrl+Shift+C for paste). That swallows SIGINT when a stale selection is present and breaks the xterm/gnome-terminal/konsole/Windows-Terminal convention where Ctrl+C in a terminal emulator is always SIGINT. The real bug was that clipboard writes lost user-gesture through OSC-52 round-trips, which the direct writeText already fixes. Fix: revert copyModifier to Ctrl+Shift+C on non-Mac. Direct writeText in the keydown handler preserves user gesture. term.write Escape replaced with term.clearSelection() (works without relying on TUI input mode). 3. Error toast text Before: 'see HERMES_TUI_DEBUG_CLIPBOARD' — tells users how to debug but not how to fix. Fix: point users at HERMES_TUI_FORCE_OSC52=1 first (the actual escape hatch), mention the debug var second.
Closed
13 tasks
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Salvages #16020 onto current main with three corrections. Replaces "copied N characters" false-positive with a truth signal that counts native-tool and tmux-buffer paths, restores the xterm Ctrl+Shift+C convention in the dashboard, and points users at
HERMES_TUI_FORCE_OSC52in the failure toast.Summary
Truth signal for
/copysetClipboard()now returns{ sequence, success }.successis true when any clipboard path was attempted: native tool fired (pbcopy/wl-copy/xclip/xsel/clip.exe), tmux buffer loaded, or an OSC 52 sequence was written./copyand the contributor's "honest feedback" toast now only report failure when literally no path was taken (e.g. headless Linux without tmux). Before this fix, local Linux + tmux users saw "clipboard copy failed" even whenxcliphad already succeeded fire-and-forget.Dashboard keybinding reverted to
Ctrl+Shift+Con non-MacBare
Ctrl+Cin a terminal emulator must stay SIGINT — matches xterm, gnome-terminal, konsole, Windows Terminal. The contributor's real fix (directnavigator.clipboard.writeText()inside the keydown handler to preserve user gesture) is kept.term.write("\x1b")replaced withterm.clearSelection()so highlight clears without relying on TUI input mode.Error toast points at the fix env var, not just the debug var
Toast now reads:
"clipboard copy failed — try HERMES_TUI_FORCE_OSC52=1 to force the escape sequence; HERMES_TUI_DEBUG_CLIPBOARD=1 for details".Also: TypeScript interfaces for
copySelection/copySelectionNoClearupdated toPromise<string>acrosshermes-ink.d.ts,interfaces.ts,use-selection.ts, and thecreateSlashHandler.test.tsmock (the contributor madeink.tsxasync but missed the declared types — CI typecheck was failing).Changes
ui-tui/packages/hermes-ink/src/ink/termio/osc.tssetClipboardreturns{sequence, success};copyNativereturnsboolean;HERMES_TUI_FORCE_OSC52env varui-tui/packages/hermes-ink/src/ink/termio/osc.test.tsHERMES_TUI_FORCE_OSC52precedenceui-tui/packages/hermes-ink/src/ink/ink.tsx{sequence, success}shape; new debug messageui-tui/packages/hermes-ink/src/ink/hooks/use-selection.tscopySelection/copySelectionNoCleartyped asPromise<string>ui-tui/src/app/interfaces.tsSelectionApi.copySelection: Promise<string>ui-tui/src/types/hermes-ink.d.tsui-tui/src/app/slash/commands/core.tsHERMES_TUI_FORCE_OSC52ui-tui/src/__tests__/createSlashHandler.test.tsPromise<string>web/src/pages/ChatPage.tsxwriteTextin keydown (contributor's fix); keybinding reverted toCtrl+Shift+C;term.clearSelection()Credits
Closes #16019.
Closes #16020.
Validation
/copy→ "copied N characters" but clipboard empty (PR #16020: "clipboard copy failed" false-negative)/copy→ "copied N characters", xclip fires, paste worksCtrl+Cwith selection (non-Mac)Ctrl+Shift+Cwith selection (non-Mac)Cmd+Cwith selection (Mac)use-selection.tsnpm run type-checkpasses)osc.test.tsHERMES_TUI_FORCE_OSC52)ui-tuitest suiteterminalSetup.test.ts/ unrelated.test.tsimport errors — same on origin/main)npm run buildnpm run lint