From f8ab362cca0f782b033d29df87bc9d9c76754002 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 20 Oct 2026 20:49:48 +0200 Subject: [PATCH] fix(coding-agent): keep clipboard helpers out of pi's process group so Ctrl+Z does not stop them closes #11179 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/utils/clipboard-command.ts | 3 +++ packages/coding-agent/test/clipboard-command.test.ts | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 6b6687d8c..34551bcbc 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -18,6 +18,7 @@ - Fixed HTML export showing literal asterisks for Markdown bold and italic next to CJK punctuation ([#3353](https://github.com/earendil-works/pi/issues/3353), [#10154](https://github.com/earendil-works/pi/issues/10154)) - Fixed the terminal staying in mouse-tracking, focus-reporting, Kitty keyboard, and alternate-screen modes when pi exits without its normal terminal teardown, which flooded the shell prompt with escape sequences ([#11170](https://github.com/earendil-works/pi/issues/11170)) - Fixed concurrent extension `select`/`confirm`/`input`/`editor` dialogs and inline `custom()` components in interactive mode: a second dialog replaced the first and the first promise never settled; closing the visible dialog now shows the previous one again ([#11166](https://github.com/earendil-works/pi/issues/11166), [#9909](https://github.com/earendil-works/pi/issues/9909)) +- Fixed pasting in the shell hanging after copying text in pi and suspending it with Ctrl+Z on Linux: the background `wl-copy`/`xclip`/`xsel` process that keeps serving the clipboard was suspended along with pi ([#11179](https://github.com/earendil-works/pi/issues/11179)) ## [1.3.0] - 2026-10-15 diff --git a/packages/coding-agent/src/utils/clipboard-command.ts b/packages/coding-agent/src/utils/clipboard-command.ts index 700fc8e60..0cddbf59c 100644 --- a/packages/coding-agent/src/utils/clipboard-command.ts +++ b/packages/coding-agent/src/utils/clipboard-command.ts @@ -8,8 +8,11 @@ export function runClipboardCommand( ): Promise { return new Promise((resolve) => { // Clipboard writers can daemonize. Do not give them output pipes to retain. + // Detach on POSIX so the daemon leaves pi's process group and Ctrl+Z suspend + // (SIGTSTP to the group) does not stop the clipboard owner (#11179). const child = spawn(command, args, { stdio: ["pipe", options?.input === undefined ? "pipe" : "ignore", "ignore"], + detached: process.platform !== "win32", windowsHide: true, }); const chunks: Buffer[] = []; diff --git a/packages/coding-agent/test/clipboard-command.test.ts b/packages/coding-agent/test/clipboard-command.test.ts index aea94ec39..8e250f817 100644 --- a/packages/coding-agent/test/clipboard-command.test.ts +++ b/packages/coding-agent/test/clipboard-command.test.ts @@ -29,6 +29,16 @@ describe("clipboard commands", () => { clearInterval(timer); } }); + // #11179: clipboard daemons must not share pi's process group, or Ctrl+Z suspend stops them. + test.skipIf(process.platform === "win32")("runs commands outside pi's process group", async () => { + const childPgid = (await runClipboardCommand("sh", ["-c", "ps -o pgid= -p $$"]))?.toString("utf8").trim(); + const ownPgid = (await runClipboardCommand("ps", ["-o", "pgid=", "-p", String(process.pid)])) + ?.toString("utf8") + .trim(); + expect(childPgid).toMatch(/^\d+$/); + expect(ownPgid).toMatch(/^\d+$/); + expect(childPgid).not.toBe(ownPgid); + }); test("rejects output above the buffer limit", async () => { expect( await runClipboardCommand(process.execPath, ["-e", "process.stdout.write(Buffer.alloc(1024))"], {