diff --git a/packages/tui/src/clipboard.ts b/packages/tui/src/clipboard.ts index cd7a6ce1a17..9374fc84417 100644 --- a/packages/tui/src/clipboard.ts +++ b/packages/tui/src/clipboard.ts @@ -51,7 +51,8 @@ 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, { + // OpenTUI rejects NUL before any destination; host clipboard text cannot contain it. + 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 6f4997f0303..a3870f62630 100644 --- a/packages/tui/test/clipboard.test.ts +++ b/packages/tui/test/clipboard.test.ts @@ -102,6 +102,19 @@ test("uses all available routes but skips the process host remotely", async () = expect(writes).toEqual({ host: 0, terminal: 1 }) }) +test("removes NUL characters before writing", async () => { + const writes: string[] = [] + const clipboard = createClipboardAdapter( + coreClipboard({ + onWrite: (text) => writes.push(text), + }), + ) + + expect(await clipboard.write("before\0after")).toBeUndefined() + expect(await clipboard.write("clean")).toBeUndefined() + expect(writes).toEqual(["beforeafter", "clean"]) +}) + test("rejects only when no clipboard route accepted the write", async () => { const writes: [string, ClipboardWriteOptions][] = [] const failure = new Error("native clipboard failed")