Skip to content

fix(actions): make copy-to-clipboard resilient - #242

Merged
LauJosefsen merged 3 commits into
mainfrom
lejo/resilient-clipboard
May 7, 2026
Merged

fix(actions): make copy-to-clipboard resilient#242
LauJosefsen merged 3 commits into
mainfrom
lejo/resilient-clipboard

Conversation

@LauJosefsen

Copy link
Copy Markdown
Collaborator

Summary

Hardens the c (copy task log) keybinding in the actions TUI so it works reliably across Wayland, X11, macOS, SSH sessions, tmux, and GNU screen — and never hangs.

Changes

  • Extract clipboard logic from actions/view.go into a dedicated actions/clipboard.go with test seams.
  • SSH bias: when SSH_TTY / SSH_CONNECTION is set, try OSC 52 first. Local xclip/pbcopy would otherwise target the server's clipboard, not the user's.
  • Per-attempt timeout (2 s) + WaitDelay (1 s): bounds every native invocation. exec.ErrWaitDelay is treated as success since xclip/xsel daemonize and hold the inherited stdin pipe (os/exec: CombinedOutput hangs when exec'd task forks a "daemon" golang/go#13155).
  • Wayland gating: wl-copy is only attempted when WAYLAND_DISPLAY resolves to a real socket under XDG_RUNTIME_DIR.
  • Tmux / GNU screen wrapping via github.com/aymanbagabas/go-osc52/v2 so OSC 52 reaches the outer terminal under tmux set-clipboard on and screen DCS pass-through.
  • Length cap (64 KB): skip OSC 52 above this — most terminals silently drop oversized sequences. Toast surfaces "log too large — press w to save to file".
  • OSC 52 emitted to stderr instead of stdout so the escape cannot race with BubbleTea's inline renderer.
  • macOS ladder narrowed to pbcopy only.
  • Toast richness: distinguishes raw / tmux-wrapped / screen-wrapped OSC 52 so users can tell why a copy may not have reached the outer terminal.
  • 15 unit tests covering candidate-ladder selection, SSH ordering bias, OSC 52 length cap, tmux/screen wrapping, and WaitDelay handling.

New dependency

github.com/aymanbagabas/go-osc52/v2 v2.0.1 — pure Go, MIT, used as a transitive dep by charmbracelet/log. Handles the OSC 52 sequence + tmux/screen DCS wrapping; keeps that complexity out of this repo.

Extract clipboard logic into actions/clipboard.go and harden it:

- Bias OSC 52 first when SSH_TTY/SSH_CONNECTION is set; native tools
  would otherwise target the SSH server's clipboard, not the user's.
- Bound each native attempt with a 2s timeout and 1s WaitDelay; treat
  exec.ErrWaitDelay as success since xclip/xsel daemonize and hold the
  inherited stdin pipe (golang/go#13155).
- Gate wl-copy on a live Wayland socket via XDG_RUNTIME_DIR/WAYLAND_DISPLAY.
- Wrap OSC 52 for tmux (\$TMUX) and GNU screen (\$STY+screen TERM) using
  the go-osc52 library so it works under tmux set-clipboard on / screen DCS
  pass-through.
- Skip OSC 52 when the payload exceeds 64 KB (most terminals silently drop
  oversized sequences) and surface a clear toast pointing to 'w' (save to
  file).
- Emit OSC 52 to stderr instead of stdout so the escape cannot race with
  the BubbleTea inline renderer.
- Restrict the macOS ladder to pbcopy (no Linux tools).
- Add unit tests covering the candidate ladder, SSH bias, OSC 52 length
  cap, tmux/screen wrapping, and WaitDelay handling.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the Actions TUI “c” (copy task log) clipboard behavior across different terminal/session environments by centralizing clipboard logic, adding environment-aware backend selection (native vs OSC 52), and adding unit tests.

Changes:

  • Extract clipboard handling into actions/clipboard.go with OSC 52 (tmux/screen wrapping), native-tool ladder, and per-attempt timeouts.
  • Update the TUI copy toast messaging in actions/view.go to reflect the backend used and handle oversized OSC 52 payloads.
  • Add actions/clipboard_test.go to cover backend ordering, environment gating, OSC 52 wrapping/limits, and exec.ErrWaitDelay handling; add go-osc52 dependency.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
go.mod Adds github.com/aymanbagabas/go-osc52/v2 dependency for OSC 52 emission/wrapping.
go.sum Records checksums for the new go-osc52 dependency.
actions/view.go Routes copy behavior through the new clipboard module and expands user-facing toast messages.
actions/clipboard.go New environment-aware clipboard implementation (native tools + OSC 52) with timeouts and test seams.
actions/clipboard_test.go New unit tests for clipboard backend selection, OSC 52 behavior, and timeout/WaitDelay behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread actions/clipboard.go
Comment thread actions/clipboard.go Outdated
Comment thread actions/view.go Outdated
- Tighten isWaylandLive: skip wl-copy when XDG_RUNTIME_DIR is unset
  rather than attempting a doomed 2 s invocation.
- Thread the resolved binary path from lookPath into exec.CommandContext
  to eliminate the duplicate PATH search and TOCTOU window.
- For oversized payloads OSC 52 now copies the last 64 KB instead of
  refusing — the tail is where task-log errors usually live, and the
  full log is still one keypress away via 'w'. Truncation respects
  UTF-8 rune boundaries.
- Toast surfaces truncation explicitly when OSC 52 was used.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the Actions TUI “copy task log” (c) behavior by extracting clipboard support into a dedicated module, adding environment-aware backend selection (native tools vs OSC 52), and bounding external invocations to prevent hangs across common terminal/session setups.

Changes:

  • Added actions/clipboard.go implementing native clipboard + OSC 52 backends with SSH/tmux/screen/Wayland-aware selection and per-attempt timeouts.
  • Updated actions/view.go to use the new clipboard API and show backend-specific copy toasts.
  • Added actions/clipboard_test.go covering backend selection, OSC 52 wrapping, truncation, and exec.ErrWaitDelay handling; added go-osc52 dependency.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
go.mod Adds github.com/aymanbagabas/go-osc52/v2 dependency for OSC 52 emission + tmux/screen wrapping.
go.sum Records checksums for the new OSC 52 dependency.
actions/view.go Switches copy handling to new copyToClipboard API and improves user-facing toast messaging by backend.
actions/clipboard.go New clipboard implementation with native tool ladder, OSC 52 fallback, SSH bias, and bounded exec behavior.
actions/clipboard_test.go New unit tests for OSC 52 emission/wrapping, candidate selection, SSH ordering, truncation, and WaitDelay behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread actions/clipboard_test.go Outdated
Comment thread actions/clipboard_test.go Outdated
Comment thread actions/clipboard.go
Comment thread actions/clipboard.go
Comment thread actions/view.go
- Decode the OSC 52 payload in the tail-truncation test rather than
  doing a base64 substring match. The substring check only worked
  because the kept portion happened to start on a 3-byte block
  boundary; with different sizes it would silently flake.
- Reconstruct the rune-boundary test so the naive cut lands on a
  UTF-8 continuation byte (asymmetric padding around a single "é"),
  and assert the decoded length to prove the cut walked forward.
@LauJosefsen
LauJosefsen merged commit 3f251fd into main May 7, 2026
12 checks passed
@LauJosefsen
LauJosefsen deleted the lejo/resilient-clipboard branch May 7, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants