Identified URL as issue, not PR.
UnderstandReviewed related issue and referenced PR.
UnderstandDetected uncommitted changes blocking git operations.
UnderstandAsked user for commit strategy on unstaged files.
PlanYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/issues/1069
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
The user wants me to review a GitHub PR. Let me start by fetching the PR details.
>gh pr view 1069 --repo badlogic/pi-mono --json title,body,comments,commits,files,author,state,labels,url>gh pr diff 1069 --repo badlogic/pi-monoIt seems this is an issue, not a PR. Let me check if it's an issue instead.
The URL points to an issue, not a PR. Let me read the issue:
>gh issue view 1069 --repo badlogic/pi-mono --json title,body,comments,labels,state,author,urlThis is an issue (#1069), not a PR. The user asked me to review it as a PR but it's actually a feature request issue. Let me clarify this to the user.
The issue is about:
/copy fails over SSH with "install xclip or xsel" errorLet me check the referenced PR #415 and the clipboard.ts file to understand the current implementation.
This is an issue (#1069), not a PR. The issue requests OSC 52 clipboard support for SSH/mosh sessions.
Let me gather more context by reading the referenced PR #415 and the current clipboard implementation:
>gh pr view 415 --repo badlogic/pi-mono --json title,body,state,mergedAt 2>/dev/null || echo "PR not found or inaccessible"Now I have a clearer picture. The issue is a feature request (not a PR) asking for OSC 52 support in the clipboard.ts file. Let me check how PR #415 implemented OSC 52 for OAuth URLs to understand the pattern.
Let me find where PR #415 implemented OSC 52 to understand the pattern:
>rg -l "OSC.*52\|\\\\x1b\]52" --type ts /Users/badlogic/workspaces/pi-mono/packages>rg -l "osc52\|OSC52\|osc 52" -i --type ts /Users/badlogic/workspaces/pi-mono/packagesNo results found for OSC 52 references. Let me search more broadly for the OAuth URL clipboard functionality.
>rg -l "OAuth\|oauth\|\\\\x1b\]" --type ts /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src | head -20>rg "52" --type ts /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src -C2 | head -50Let me search for the mobile SSH clipboard support mentioned in PR #415 description using different patterns.
>rg "clipboard" --type ts /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src -l>git log --oneline --grep="OSC" --all 2>/dev/null | head -10Hmm, the git history doesn't show a commit for OSC 52 from PR #415. The user might have been mistaken about PR #415 implementing OSC 52, or it may not be in this repo. Regardless, this is just an issue asking for OSC 52 support, not a PR to review.
Let me clarify the situation for the user.
Issue #1069 Summary (Not a PR)
This is a feature request issue, not a pull request. There is no code to review.
Issue Details:
Request:
The /copy command fails over SSH with "install xclip or xsel" even though the user's terminal supports OSC 52 escape sequences. The issue asks for pi to detect remote sessions and use OSC 52 (terminal-based clipboard) instead of relying on native clipboard tools.
Current Implementation:
packages/coding-agent/src/utils/clipboard.ts only supports:
pbcopyclipwl-copy (Wayland), xclip/xsel (X11)No fallback for remote/SSH sessions.
User's Proof of Concept: The user provided a working extension that uses OSC 52:
function copyViaOSC52(text: string): void { const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`);}Referenced PR #415:
Merged 2026-01-03. Added OSC 52 for OAuth login URLs specifically for mobile SSH users. However, I found no OSC 52 code in the current codebase (the commit d42a3d71 mentions "make OAuth login URL clickable" but uses OSC 8 hyperlinks, not OSC 52).
Next Steps: If you want me to implement this feature, I can:
SSH_CLIENT, SSH_TTY, SSH_CONNECTION env vars)copyToClipboard() in clipboard.tsWould you like me to proceed with implementation?
any harm in just emitting that anyways? irrespective of whether the terminal supports it or not? any harm in that?
The user is asking whether we can just always emit OSC 52 escape sequences regardless of terminal support, instead of detecting SSH sessions.
Let me think about the potential downsides:
Visual noise: If a terminal doesn't support OSC 52, it might display garbage characters or the raw escape sequence. But most modern terminals either support it or silently ignore it.
Security: Some terminals disable OSC 52 by default for security reasons (a malicious program could read clipboard contents with OSC 52 query). But we're only writing, not reading.
Terminal compatibility: Most modern terminals (iTerm2, Terminal.app, GNOME Terminal, Konsole, Windows Terminal, Alacritty, kitty, tmux, etc.) either support OSC 52 or ignore it silently.
tmux/screen: These terminal multiplexers have OSC 52 support but it's sometimes disabled by default. When disabled, they typically just ignore the sequence.
Size limits: Some terminals have limits on OSC 52 payload size (e.g., tmux defaults to 1MB). Large copies might be silently truncated.
The worst case for unsupported terminals is usually silent failure - the sequence is ignored. Very old or unusual terminals might print garbage, but that's rare.
The simplest approach would be:
Or even simpler: always emit OSC 52 in addition to trying native tools. The overhead is minimal.
Actually, re-reading the issue - the problem is that on SSH, native tools fail and throw an error. So the question is: should we just always emit OSC 52 (either instead of or in addition to native tools)?
If we emit it in addition to native tools, there's essentially no harm - it's just a few bytes written to stdout.
If we use it as a fallback when native tools fail, the main tradeoff is that we can't verify whether OSC 52 actually succeeded, so the user might be uncertain if the copy worked.
No real harm. The possibilities:
The only minor issues:
Simplest approach: always emit OSC 52 as a fallback when native tools fail. Change the current "throw error" path to "try OSC 52, then succeed silently with a hopeful message."
// Instead of throwing, fall back to OSC 52const encoded = Buffer.from(text).toString("base64");process.stdout.write(`\x1b]52;c;${encoded}\x07`);This way:
The user gets clipboard functionality in more scenarios with no downside. Want me to implement it?
does that fix the users issue?
if we only emit if native tools failß
The user is asking if falling back to OSC 52 only when native tools fail would fix the issue described in #1069.
Let me re-read the issue:
/copy fails over SSH with "install xclip or xsel"So yes - the user is running pi over SSH. On the remote machine, there's no xclip/xsel installed (or no X display), so the native tools fail. Currently this throws an error. If we emit OSC 52 as a fallback when native tools fail, the user's terminal (which supports OSC 52) would receive the sequence and copy to the local clipboard.
So yes, this would fix the user's issue. The native tools fail → OSC 52 is emitted → user's terminal (which supports OSC 52) copies to clipboard.
The user is asking a clarifying question to make sure the proposed solution actually addresses the problem. I should confirm that yes, it does.
Yes. The user's scenario:
The OSC 52 sequence travels through the SSH connection to the user's terminal emulator, which handles the clipboard operation locally. That's exactly what the user wants.
what if the remote has xclip/xsel?
Good question. If the remote machine has xclip/xsel installed:
So in the case where xclip/xsel is installed but there's no X display, the native tool would likely fail and we'd fall back to OSC 52 - that works.
But if xclip/xsel is installed AND X forwarding is active (or the remote has a display), the native tool would "succeed" but copy to the wrong clipboard (remote, not local). The user wouldn't get the text in their local clipboard.
So the "only fallback on failure" approach has a gap: when native tools are installed but copy to the wrong place (remote clipboard instead of local).
The user is pointing out this edge case. The better solution might be:
Or even simpler: always emit OSC 52 in addition to native tools. Then:
The cleanest fix is probably: detect SSH and use OSC 52 directly in that case, skipping native tools.
Good catch. If remote has xclip/xsel:
So "fallback on failure" doesn't cover this case. Need to detect SSH and prefer OSC 52:
const isSSH = !!(process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.SSH_CONNECTION);
if (isSSH) { // Emit OSC 52 - goes to user's local terminal const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`); return;}// Otherwise use native tools...Or emit both (native + OSC 52) always, but that's wasteful and could cause double-copy weirdness on local sessions.
SSH detection is the cleaner approach.
why not just always emit this shit?
The user is asking why not just always emit OSC 52, regardless of SSH or native tools. Let me think about what could go wrong:
Local terminal session with native tools: OSC 52 emitted, terminal copies to clipboard. Native tools also copy to clipboard. Result: text copied twice to same clipboard. No harm.
Local terminal that doesn't support OSC 52: Sequence ignored or rarely shows garbage. Native tools still work. No harm.
SSH session: OSC 52 works, native tools either fail or copy to remote clipboard (useless). OSC 52 is the one that matters. Works.
tmux/screen: Usually supports OSC 52. If disabled, ignored. Native tools might or might not work depending on setup.
Actually... why bother with native tools at all? Just emit OSC 52 and be done with it?
Wait, there are some cases where OSC 52 doesn't work well:
Hmm, the only real argument for keeping native tools:
But in practice, most modern terminals support OSC 52. And the worst case is silent failure.
The user's point is valid: just always emit OSC 52. Either:
The simplest approach: just emit OSC 52, period. Remove all the platform-specific native tool logic.
Actually wait - there might be cases where pi is run in contexts where stdout isn't a terminal but clipboard operations are still expected to work (e.g., some CI/CD, scripts). In those cases native tools would work but OSC 52 wouldn't.
For an interactive coding agent though, that's not really relevant—the user is always working at a terminal. The user's right: just emit OSC 52 and keep it simple. It works everywhere that matters for this use case, and there's no real downside.
You're right. Just always emit OSC 52:
The whole function becomes:
export function copyToClipboard(text: string): void { const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`);}Delete all the platform detection, native tool fallbacks, Wayland vs X11 logic. 50 lines → 3 lines.
Only theoretical downside: terminals with OSC 52 disabled (rare, usually security-paranoid setups). But those users probably know what they're doing and can enable it.
Want me to implement this?
no, we need to keep the native tools, we just emit this on top of that?
Ah, the user wants to keep the native tools but also always emit OSC 52 on top. So:
This gives belt-and-suspenders:
Makes sense. The user wants robustness, not minimalism.
Got it. Belt and suspenders:
export function copyToClipboard(text: string): void { // Always emit OSC 52 - works over SSH, harmless locally const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`); // Also try native tools (best effort, don't throw on failure) try { // ... existing platform logic ... } catch { // Ignore - OSC 52 already emitted }}Want me to implement?
yes
The user wants me to implement the change. Let me read the current clipboard.ts file again to make sure I have the exact content, then make the edit.
Now I'll implement the change:
Now I need to:
npm run check to make sure there are no errors>cd /Users/badlogic/workspaces/pi-mono && npm run check 2>&1There's an existing error in daxnuts.ts that's unrelated to my changes. Let me check if this existed before my changes by looking at what the issue is. Actually, looking at the error message, it's a TypeScript error that's unrelated to my clipboard changes. It says "Untyped function calls may not accept type arguments" in daxnuts.ts. This is a pre-existing issue.But wait, the AGENTS.md says: "After code changes (not documentation changes): npm run check (get full output, no tail). Fix all errors, warnings, and infos before committing."Let me check if this error was there before my changes. I'll check the daxnuts.ts file to understand what's happening there.Actually, this is an existing error in the codebase that I shouldn't need to fix for this change. Let me verify my clipboard changes don't introduce new errors. The error and info in the output are both from daxnuts.ts, not from my clipboard changes.
Let me check if there are any issues specifically with my changes by looking at what biome found. The only changes it made were auto-fixes, and the error is in a completely different file.
Let me just add the changelog entry and inform the user about the pre-existing error.
Pre-existing error in daxnuts.ts (unrelated to my changes). Let me add the changelog entry:
I need to add an entry under ### Added for OSC 52 clipboard support. Let me add it after the existing ### Added entry.
Done. Let me summarize what I did.
Done. Changes:
packages/coding-agent/src/utils/clipboard.ts
copyToClipboard()packages/coding-agent/CHANGELOG.md
### AddedThe /copy command now:
Note: Pre-existing TS error in daxnuts.ts unrelated to this change.
ok, commited and pushed?
The user is asking if the changes have been committed and pushed. They haven't been - I made the changes but didn't commit. The user is asking me to do that now.
Let me commit and push. I need to:
>cd /Users/badlogic/workspaces/pi-mono && git statusI see there are other modified files that I didn't change (interactive-mode.ts, components/index.ts) and untracked files (daxnuts.ts). I should only commit the files I modified:
Let me add only those and commit.
>cd /Users/badlogic/workspaces/pi-mono && git add packages/coding-agent/CHANGELOG.md packages/coding-agent/src/utils/clipboard.ts && git commit -m "feat(coding-agent): add OSC 52 clipboard support for SSH/mosh sessions
Fixes #1069"Commit succeeded. Now let me push.
>cd /Users/badlogic/workspaces/pi-mono && git pushNeed to pull first and rebase.
>cd /Users/badlogic/workspaces/pi-mono && git pull --rebase && git pushThere are unstaged changes from other agents. I can't stash them (that would affect other agents' work). Let me check what files have unstaged changes.
>cd /Users/badlogic/workspaces/pi-mono && git diff --name-onlyThere are unstaged changes from another agent in those two files. I can't stash (would affect their work). I could try to temporarily add those files, pull --rebase, then restore them. But that's risky.
Actually, I can use git fetch and git rebase with autostash disabled. Let me try a different approach - fetch first, then rebase only my commit.
Actually, the safest approach here is to ask the user what to do, since there are uncommitted changes from other agents that I can't touch.
There are uncommitted changes from another agent in:
packages/coding-agent/src/modes/interactive/components/index.tspackages/coding-agent/src/modes/interactive/interactive-mode.tsI can't stash or checkout those files (would destroy their work). Options:
What would you like me to do?
are we good to commit anow? also have changelog entries
The user is saying that the other changes are now ready to commit and have changelog entries. They want me to check if we're good to commit now. Let me check the git status and see if the other files are ready.
>cd /Users/badlogic/workspaces/pi-mono && git statusIt looks like the other agent added their changes and now there are modified files ready to be staged. There's also a new untracked file daxnuts.ts.
The user wants me to commit these - but I shouldn't commit changes from another agent. My commit for OSC 52 is already done (branch ahead by 1 commit).
I think the user wants me to push my commit now that the worktree is clean (the other changes can be committed separately). But there are still unstaged changes. Let me try to pull --rebase again - wait, that won't work because of unstaged changes.
Actually, my commit is already done. I just need to push. But the unstaged changes are blocking the rebase. Let me see if I can fetch and rebase without affecting the unstaged files.
Actually, I can try git fetch then git rebase origin/main - but that still won't work with unstaged changes.
Hmm, let me ask the user - do they want me to commit ALL the changes including the daxnuts stuff? That seems like what they're suggesting with "are we good to commit now? also have changelog entries".
My OSC 52 commit is already done (branch ahead by 1). But I can't push because git pull --rebase fails with unstaged changes.
Are you asking me to commit the daxnuts changes too? If so, I'd be committing another agent's work. Should I:
Which do you prefer?
function copyViaOSC52(text: string): void { const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`);}// Instead of throwing, fall back to OSC 52const encoded = Buffer.from(text).toString("base64");process.stdout.write(`\x1b]52;c;${encoded}\x07`);const isSSH = !!(process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.SSH_CONNECTION);
if (isSSH) { // Emit OSC 52 - goes to user's local terminal const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`); return;}// Otherwise use native tools...export function copyToClipboard(text: string): void { const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`);}export function copyToClipboard(text: string): void { // Always emit OSC 52 - works over SSH, harmless locally const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`); // Also try native tools (best effort, don't throw on failure) try { // ... existing platform logic ... } catch { // Ignore - OSC 52 already emitted }}