From 836402c2c4889cb9612bdced30e16897aca03c00 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Tue, 13 Oct 2026 18:17:18 +0200 Subject: [PATCH] fix(terminal): a replay strips the 8-bit C1 form of OSC 52 too (#611 merge-time fix) xterm.js still parses OSC 52 introduced by C1 OSC (\x9d) and ended by C1 ST (\x9c). OSC52_STRIP_RE matched only the 7-bit forms, so a direct-PTY session that emitted the C1 form re-copied old text on every replay. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/web/public/constants.js | 5 +++-- test/terminal-osc52-clipboard.test.ts | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/web/public/constants.js b/src/web/public/constants.js index 7c3536b5..fa8f5236 100644 --- a/src/web/public/constants.js +++ b/src/web/public/constants.js @@ -1566,8 +1566,9 @@ const DEC_SYNC_STRIP_RE = /\x1b\[\?2026[hl]/g; // OSC 52 clipboard writes stripping — a replay re-feeds the raw byte history, and // xterm parses it after chunkedTerminalWrite has already released the load -// gate, so an OSC 52 left in would re-copy old text on every tab switch. -const OSC52_STRIP_RE = /\x1b\]52;[^\x07\x1b]*(?:\x07|\x1b\\)/g; +// gate, so an OSC 52 left in would re-copy old text on every tab switch. The +// 8-bit C1 forms (OSC \x9d, ST \x9c) too: xterm.js still parses those. +const OSC52_STRIP_RE = /(?:\x1b\]|\x9d)52;[^\x07\x1b\x9c]*(?:\x07|\x1b\\|\x9c)/g; // The same OSC 52 text arriving again within this window is one copy delivered // several ways (direct, tmux load-buffer -w, tmux set-clipboard forwarding). const OSC52_DEDUPE_MS = 1000; diff --git a/test/terminal-osc52-clipboard.test.ts b/test/terminal-osc52-clipboard.test.ts index 67bb369d..c97bd01e 100644 --- a/test/terminal-osc52-clipboard.test.ts +++ b/test/terminal-osc52-clipboard.test.ts @@ -208,6 +208,8 @@ describe('main-terminal replay over a real xterm', () => { const re = (context.globalThis as { __re: RegExp }).__re; const input = `a\x1b]52;c;${b64('x')}\x07b\x1b]52;;${b64('y')}\x1b\\c\x1b]0;title\x07d`; expect(input.replace(re, '')).toBe('abc\x1b]0;title\x07d'); + // The 8-bit C1 forms (OSC \x9d, ST \x9c), which xterm.js also parses. + expect(`a\x9d52;c;${b64('x')}\x9cb\x1b]52;c;${b64('y')}\x9cc`.replace(re, '')).toBe('abc'); }); it('still copies a live OSC 52 written after the replay', async () => {