Skip to content

Fix Linux clipboard copy by adding OSC52 fallback#225

Open
Aditya190803 wants to merge 3 commits intodavis7dotsh:mainfrom
Aditya190803:fix/linux-clipboard-osc52-fallback
Open

Fix Linux clipboard copy by adding OSC52 fallback#225
Aditya190803 wants to merge 3 commits intodavis7dotsh:mainfrom
Aditya190803:fix/linux-clipboard-osc52-fallback

Conversation

@Aditya190803
Copy link
Copy Markdown

@Aditya190803 Aditya190803 commented Apr 18, 2026

What happened

I tried using btca on Linux, and clipboard copy failed in my terminal setup.

Root cause

On Linux, clipboard copy relied on external binaries (wl-copy, xclip, xsel). In minimal or terminal-only environments, these may be unavailable, causing copy to fail.

What I changed

  • Added an OSC52 fallback for Linux clipboard copy when system clipboard tools are unavailable.
  • Improved async TUI error handling so failures are surfaced as proper Error values.
  • Updated docs to describe Linux clipboard fallback behavior.
  • Added tests for clipboard fallback behavior.

Why this fix

OSC52 works through compatible terminals without requiring external clipboard binaries, making clipboard copy more reliable for Linux users.

  • Added OSC52 clipboard support:
    • New exported functions in apps/cli/src/tui/clipboard.ts:
      • getOsc52Sequence(text: string): string — generates Base64-encoded OSC52 UTF-8 escape.
      • tryOsc52Copy(text: string, options?): boolean — writes OSC52 to stdout when appropriate and returns success flag.
  • Implemented OSC52 fallback logic for Linux clipboard:
    • Linux/WSL copy flow now attempts: clip.exe (WSL) → wl-copy → xclip → xsel, then OSC52 fallback if external commands are unavailable.
    • OSC52 is only attempted when stdout is a TTY and TERM is not "dumb".
    • When all methods fail, the thrown error now reports both external command failures and OSC52 unavailability.
  • Tests:
    • Added apps/cli/src/tui/clipboard.test.ts to validate OSC52 sequence generation and tryOsc52Copy behavior under different terminal conditions (isTTY false, TERM=dumb, TERM supporting OSC52).
  • Async error handling improvements:
    • Normalized error handling by converting non-Error throwables into Error instances via explicit Effect.tryPromise({ try, catch }) or equivalent in:
      • apps/cli/src/tui/context/messages-context.tsx
      • apps/cli/src/tui/services.ts
  • Documentation updates:
    • apps/docs/btca.spec.md and apps/docs/guides/cli-reference.mdx updated to document Linux/WSL clipboard behavior and OSC52 caveats.
  • Review/regression note:
    • Automated review flagged a potential regression where a successful xclip call might not return early and could fall through to writing OSC52 to stdout; tests were added to cover expected behavior.
  • No changes to authentication service implementation or database schema/data model.

Note

Fix Linux clipboard copy by adding OSC52 terminal fallback

  • Adds getOsc52Sequence and tryOsc52Copy helpers in clipboard.ts that base64-encode text and write an OSC52 escape sequence to stdout as a last-resort clipboard mechanism.
  • copyToClipboard on Linux now tries wl-copy, xclip, xsel, and OSC52 in order before throwing; the final error message text has also changed.
  • Fixes non-Error rejections from copyToClipboard in messages-context.tsx by coercing them to Error instances before propagation.
  • OSC52 copy is skipped when stdout is not a TTY or TERM is dumb.
  • Documents the fallback chain and OSC52 terminal dependency in cli-reference.mdx and btca.spec.md.

Macroscope summarized 527536c.

Greptile Summary

This PR adds an OSC52 terminal escape sequence fallback for Linux clipboard copy when external binaries (wl-copy, xclip, xsel) are unavailable, along with improved Effect.tryPromise error coercion and documentation updates. The early-return control flow for xclip/xsel is correct and the previously flagged xclip fallthrough regression is resolved.

Confidence Score: 5/5

Safe to merge — the OSC52 fallback logic is correct, the xclip early-return regression is fixed, and error coercion improvements are sound.

All findings are P2 style suggestions. No regressions in existing behavior were introduced; the only note is a pre-existing limitation in runClipboard that reduces OSC52 fallback coverage for installed-but-failing binaries.

apps/cli/src/tui/clipboard.ts — the runClipboard helper should check exit codes to make OSC52 fallback fully effective.

Important Files Changed

Filename Overview
apps/cli/src/tui/clipboard.ts Adds OSC52 fallback for Linux clipboard copy; early-return logic for xclip/xsel is correct, but runClipboard still silently succeeds on non-zero exit codes (pre-existing issue)
apps/cli/src/tui/clipboard.test.ts New test file covering OSC52 sequence generation and TTY/dumb-terminal guard conditions
apps/cli/src/tui/context/messages-context.tsx Effect.tryPromise updated to explicit catch form to properly coerce non-Error throws into Error instances; functionally safe change
apps/cli/src/tui/services.ts Same Effect.tryPromise catch-coercion improvement as messages-context; no behavioral regression
apps/docs/btca.spec.md Documents Linux clipboard fallback chain including OSC52 caveats; accurate and clear
apps/docs/guides/cli-reference.mdx User-facing docs updated to describe OSC52 fallback behavior on Linux
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/cli/src/tui/clipboard.ts
Line: 64-74

Comment:
**`runClipboard` treats any completed process as success**

`await proc.exited` resolves to the exit code number and does not throw on non-zero. If `xclip` or `xsel` is installed but fails (e.g. no `$DISPLAY`, no X server running), `runClipboard` returns `true` and the early `return` fires — bypassing the OSC52 fallback entirely, which is the whole point of this PR. The user sees "Copied to clipboard" even though nothing was copied.

```ts
const runClipboard = async (command: string[]) => {
    try {
        const proc = spawn(command, { stdin: 'pipe' });
        proc.stdin.write(text);
        proc.stdin.end();
        const exitCode = await proc.exited;
        return exitCode === 0;
    } catch {
        return false;
    }
};
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "fix(docs): update Linux clipboard behavi..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 18, 2026

@Aditya190803 is attempting to deploy a commit to the davis7dotsh Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

Adds OSC52 terminal-clipboard fallback and tests, introduces getOsc52Sequence and tryOsc52Copy, updates Linux clipboard control flow to attempt OSC52 after external tools fail, normalizes non-Error throwables in several Effect.tryPromise calls, and updates CLI docs for Linux clipboard behavior.

Changes

Cohort / File(s) Summary
OSC52 clipboard implementation & tests
apps/cli/src/tui/clipboard.ts, apps/cli/src/tui/clipboard.test.ts
Added getOsc52Sequence(text) and tryOsc52Copy(text, options); updated Linux copyToClipboard to attempt OSC52 after wl-copy/xclip/xsel failures; new tests validate OSC52 sequence and behavior across isTTY and TERM scenarios.
Effect error normalization & SSE handling
apps/cli/src/tui/context/messages-context.tsx, apps/cli/src/tui/services.ts
Refactored Effect.tryPromise calls to object form with catch mapping non-Error throwables to Error; adjusted SSE done handling structure inside askQuestion.
Documentation
apps/docs/btca.spec.md, apps/docs/guides/cli-reference.mdx
Added Linux clipboard behavior docs describing fallback order (WSL clip.exewl-copyxclipxsel → OSC52) and noted OSC52 depends on terminal/SSH configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title "Fix Linux clipboard copy by adding OSC52 fallback" directly and clearly describes the main change: adding an OSC52 fallback mechanism to fix clipboard copy issues on Linux.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines 89 to 100
const xclipResult = await runClipboard(['xclip', '-selection', 'clipboard']);
if (!xclipResult) {
const xselResult = await runClipboard(['xsel', '--clipboard', '--input']);
if (!xselResult) {
throw new Error('Failed to copy to clipboard: no compatible clipboard command succeeded.');
}
if (xselResult) return;
}

const osc52Result = tryOsc52Copy(text);
if (!osc52Result) {
throw new Error(
'Failed to copy to clipboard: no compatible clipboard command succeeded and terminal OSC52 fallback is unavailable.'
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 xclip success falls through to OSC52

When xclipResult is true (xclip succeeds), the code skips the inner if (!xclipResult) block but still reaches tryOsc52Copy, writing a raw OSC52 escape sequence to stdout on top of the already-completed copy. Before this PR, a successful xclip caused the function to return via implicit fall-off. The refactor dropped that early-exit path.

Suggested change
const xclipResult = await runClipboard(['xclip', '-selection', 'clipboard']);
if (!xclipResult) {
const xselResult = await runClipboard(['xsel', '--clipboard', '--input']);
if (!xselResult) {
throw new Error('Failed to copy to clipboard: no compatible clipboard command succeeded.');
}
if (xselResult) return;
}
const osc52Result = tryOsc52Copy(text);
if (!osc52Result) {
throw new Error(
'Failed to copy to clipboard: no compatible clipboard command succeeded and terminal OSC52 fallback is unavailable.'
);
}
const xclipResult = await runClipboard(['xclip', '-selection', 'clipboard']);
if (xclipResult) return;
const xselResult = await runClipboard(['xsel', '--clipboard', '--input']);
if (xselResult) return;
const osc52Result = tryOsc52Copy(text);
if (!osc52Result) {
throw new Error(
'Failed to copy to clipboard: no compatible clipboard command succeeded and terminal OSC52 fallback is unavailable.'
);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/cli/src/tui/clipboard.ts
Line: 89-100

Comment:
**xclip success falls through to OSC52**

When `xclipResult` is `true` (xclip succeeds), the code skips the inner `if (!xclipResult)` block but **still reaches `tryOsc52Copy`**, writing a raw OSC52 escape sequence to stdout on top of the already-completed copy. Before this PR, a successful `xclip` caused the function to return via implicit fall-off. The refactor dropped that early-exit path.

```suggestion
		const xclipResult = await runClipboard(['xclip', '-selection', 'clipboard']);
		if (xclipResult) return;

		const xselResult = await runClipboard(['xsel', '--clipboard', '--input']);
		if (xselResult) return;

		const osc52Result = tryOsc52Copy(text);
		if (!osc52Result) {
			throw new Error(
				'Failed to copy to clipboard: no compatible clipboard command succeeded and terminal OSC52 fallback is unavailable.'
			);
		}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/cli/src/tui/clipboard.ts (1)

64-73: ⚠️ Potential issue | 🟠 Major

Exit code not checked for clipboard command execution.

At lines 57, 62, and 69, spawn processes are awaited but their exit codes are ignored. Line 69 is particularly problematic—runClipboard always returns true regardless of command success, which prevents the OSC52 fallback at line 95 from executing when all clipboard commands fail. This results in false "copied" outcomes.

Proposed fix
		const runClipboard = async (command: string[]) => {
			try {
				const proc = spawn(command, { stdin: 'pipe' });
				proc.stdin.write(text);
				proc.stdin.end();
-				await proc.exited;
-				return true;
+				const exitCode = await proc.exited;
+				return exitCode === 0;
			} catch {
				return false;
			}
		};

Also apply the same fix to the darwin and win32 branches at lines 54–57 and 59–62 respectively.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/tui/clipboard.ts` around lines 64 - 73, runClipboard currently
awaits the spawned process but always returns true, so clipboard command
failures are ignored; update runClipboard to inspect the process exit
code/status (from the spawned proc) and return true only when the exit code is
zero (success), otherwise return false so the OSC52 fallback can run. Apply the
same fix to the platform-specific clipboard branches (the darwin and win32
spawn/await blocks) so they also check the process exit code/status and only
treat zero as success.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs/btca.spec.md`:
- Around line 190-191: Update the docs to reflect the actual clipboard detection
order used in apps/cli/src/tui/clipboard.ts: on WSL the code checks for clip.exe
(via Windows paths) first, then falls back to Wayland/X11 tools (wl-copy, xclip,
xsel) and finally OSC52; change the sentence that currently lists wl-copy first
to state the WSL-first ordering (clip.exe → wl-copy → xclip → xsel → OSC52) so
it matches the implementation in the clipboard handling code (see the
copyToClipboard / clipboard command resolution logic in
apps/cli/src/tui/clipboard.ts).

In `@apps/docs/guides/cli-reference.mdx`:
- Around line 46-47: Update the Linux clipboard paragraph to include the WSL
branch: state that on WSL btca first tries Windows clipboard via clip.exe, then
proceeds to Wayland (`wl-copy`), then `xclip`, then `xsel`, and finally falls
back to terminal OSC52; ensure the text mentions `clip.exe` and OSC52 so the
order matches the implementation.

---

Outside diff comments:
In `@apps/cli/src/tui/clipboard.ts`:
- Around line 64-73: runClipboard currently awaits the spawned process but
always returns true, so clipboard command failures are ignored; update
runClipboard to inspect the process exit code/status (from the spawned proc) and
return true only when the exit code is zero (success), otherwise return false so
the OSC52 fallback can run. Apply the same fix to the platform-specific
clipboard branches (the darwin and win32 spawn/await blocks) so they also check
the process exit code/status and only treat zero as success.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d6c2bf23-bbb9-450c-b7fc-543564b5dff3

📥 Commits

Reviewing files that changed from the base of the PR and between 864e5ba and 16ea96a.

📒 Files selected for processing (6)
  • apps/cli/src/tui/clipboard.test.ts
  • apps/cli/src/tui/clipboard.ts
  • apps/cli/src/tui/context/messages-context.tsx
  • apps/cli/src/tui/services.ts
  • apps/docs/btca.spec.md
  • apps/docs/guides/cli-reference.mdx

Comment thread apps/docs/btca.spec.md Outdated
Comment thread apps/docs/guides/cli-reference.mdx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant