From 18f5929d982355b384328ef90ea39e76bb9f83dc Mon Sep 17 00:00:00 2001 From: fQwQf Date: Tue, 25 Aug 2026 01:10:07 +0800 Subject: [PATCH] fix(tui): omit NUL characters in clipboard writes Host clipboards reject NUL characters, so /copy failed entirely when a transcript contained a NUL byte from shell output (#44198). Strip NULs in the clipboard adapter so the copy succeeds without them. --- packages/tui/src/clipboard.ts | 4 +++- packages/tui/test/clipboard.test.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/tui/src/clipboard.ts b/packages/tui/src/clipboard.ts index cd7a6ce1a170..015b92fe6460 100644 --- a/packages/tui/src/clipboard.ts +++ b/packages/tui/src/clipboard.ts @@ -51,7 +51,9 @@ export function createClipboardAdapter(clipboard: CoreClipboardService): OwnedCl throw new Error(`Unexpected clipboard MIME type: ${result.representation.mimeType}`) }, async write(text) { - const result = await clipboard.writeText(text, { + // Host clipboards reject NUL characters (e.g. from shell tool output); + // omit them so a single stray byte cannot fail the whole copy. + const result = await clipboard.writeText(text.replaceAll("\0", ""), { destination: "all-available", selection: "clipboard", }) diff --git a/packages/tui/test/clipboard.test.ts b/packages/tui/test/clipboard.test.ts index 6f4997f03032..c75061ec6a89 100644 --- a/packages/tui/test/clipboard.test.ts +++ b/packages/tui/test/clipboard.test.ts @@ -102,6 +102,18 @@ test("uses all available routes but skips the process host remotely", async () = expect(writes).toEqual({ host: 0, terminal: 1 }) }) +test("omits NUL characters that host clipboards cannot carry", async () => { + const writes: [string, ClipboardWriteOptions][] = [] + const clipboard = createClipboardAdapter( + coreClipboard({ + onWrite: (text, input) => writes.push([text, input]), + }), + ) + + await clipboard.write("a\0b\0") + expect(writes).toEqual([["ab", { destination: "all-available", selection: "clipboard" }]]) +}) + test("rejects only when no clipboard route accepted the write", async () => { const writes: [string, ClipboardWriteOptions][] = [] const failure = new Error("native clipboard failed")