From 3349e1db180045ce0abc657e7e2411433e50ba41 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 15 Sep 2026 23:08:14 +0200 Subject: [PATCH] fix(coding-agent): reject unverified local clipboard writes fixes #9618 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/utils/clipboard.ts | 2 +- packages/coding-agent/test/clipboard.test.ts | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a1f0789b1..ff2989315 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixed +- Fixed local clipboard failures reporting success when the terminal ignored the fallback OSC 52 write ([#9618](https://github.com/earendil-works/pi/issues/9618)). - Capped agent-level retry backoff at `retry.maxAgentDelayMs` (60s by default) so long retry runs stay responsive during prolonged transient outages ([#8826](https://github.com/earendil-works/pi/issues/8826)). - Fixed direct RPC `steer` and `follow_up` commands bypassing extension `input` handlers ([#8718](https://github.com/earendil-works/pi/issues/8718)). - Fixed premature missing-model errors after login by waiting for catalog discovery. Radius now defaults to `balanced`, falling back to the first available Radius model when needed. diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index 0e268dd0b..3ff6b5881 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -72,6 +72,6 @@ export async function copyToClipboard(text: string): Promise { } } } - if (isRemoteSession() || !copied) copied = emitOsc52(text) || copied; + if (isRemoteSession()) copied = emitOsc52(text) || copied; if (!copied) throw new Error("Failed to copy to clipboard"); } diff --git a/packages/coding-agent/test/clipboard.test.ts b/packages/coding-agent/test/clipboard.test.ts index c8b390346..ee1481003 100644 --- a/packages/coding-agent/test/clipboard.test.ts +++ b/packages/coding-agent/test/clipboard.test.ts @@ -157,13 +157,24 @@ describe("copyToClipboard", () => { expect(mocks.command.mock.calls.map(([name]) => name)).toEqual(["wl-copy", "xclip", "xsel"]); expect(osc52Writes).toHaveLength(0); }); - test("uses OSC 52 when native and command writes fail", async () => { + test("local Linux failure does not report an unverified OSC 52 write as success", async () => { + // Regression test for #9618. + mocks.platform.mockReturnValue("linux"); + vi.stubEnv("DISPLAY", ":0"); + mocks.command.mockResolvedValue(undefined); + await expect(copyToClipboard("hello")).rejects.toThrow("Failed to copy to clipboard"); + expect(mocks.command.mock.calls.map(([name]) => name)).toEqual(["xclip", "xsel"]); + expect(osc52Writes).toHaveLength(0); + }); + test("uses OSC 52 when native and command writes fail in a remote session", async () => { + vi.stubEnv("SSH_CONNECTION", "client server"); mocks.clipboard.setText.mockRejectedValue(new Error("native failed")); mocks.command.mockResolvedValue(undefined); await copyToClipboard("hello"); expect(osc52Writes).toHaveLength(1); }); test("does not emit oversized OSC 52 payloads", async () => { + vi.stubEnv("SSH_CONNECTION", "client server"); mocks.clipboard.setText.mockRejectedValue(new Error("native failed")); mocks.command.mockResolvedValue(undefined); await expect(copyToClipboard("x".repeat(80_000))).rejects.toThrow("Failed to copy to clipboard");