# \[Bug\]: Large terminal clipboard sequences repeatedly rescan pending history · Issue \#23817 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/23817)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: completed

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/23817)

---

## Area

apps/server

## Steps to reproduce

1. Use main at `22840ab943b8418744b48223411e46ba4fdcef1a` on Linux x64, Node 26.12.1.
2. Emit an OSC 52 clipboard-write sequence for 256 KiB of text from a terminal process, split across typical 4 KiB PTY chunks. Editors and terminal applications emit these sequences for clipboard copies.
3. Run the reproduction below, or inspect `sanitizeTerminalHistoryChunk` in `apps/server/src/terminal/Manager.ts` as each chunk arrives.

## Expected behavior

Scan new bytes for the string terminator, preserving split terminators and the exact saved history.

## Actual behavior

Each output event joins the pending control string to the new data and scans the accumulated string from the beginning. The 256 KiB fixture visits 15,320,244 characters for a 349,536-character wire sequence. This runs synchronously before output publication.

| Same 256 KiB OSC 52 payload, 4 KiB chunks | Baseline | Patch |
|---|---:|---:|
| Median (min–max), 7 trials | 34.481 ms (34.272–39.601) | 1.951 ms (1.533–2.152) |

Both versions produce identical output. A focused patch scans only new data plus one previous character. A 1 MiB fixture falls from 240,239,284 character visits to 1,398,451. Replaying captured PTY chunks also reproduces the cost.

## Impact and environment

Occasional CPU and output-delivery cost when a terminal application emits a large valid control string. Linux x64, Intel i5-14400F, Node 26.12.1. These are aggregate sanitizer timings, not a measured end-to-end client delay or one uninterrupted stall. Growing-string copying remains a separate cost.

<details>
<summary>Self-contained before/after reproduction</summary>

Run from the PR checkout with Node 26.12.1. This loads the exact baseline and patched production functions and checks output equality. No third-party benchmark dependency is needed.

```sh
node --input-type=module <<'JS'
import * as assert from 'node:assert/strict';
import * as fs from 'node:fs';
import { execFileSync } from 'node:child_process';
import { stripTypeScriptTypes } from 'node:module';
import { performance } from 'node:perf_hooks';

const base = '22840ab943b8418744b48223411e46ba4fdcef1a';
const file = 'apps/server/src/terminal/Manager.ts';
async function load(source, name) {
  const start = source.indexOf('function isCsiFinalByte(');
  const end = source.indexOf('\nfunction legacySafeThreadId(', start);
  assert.ok(start >= 0 && end > start);
  const js = stripTypeScriptTypes(source.slice(start, end) + '\nexport { sanitizeTerminalHistoryChunk };');
  return import(`data:text/javascript;base64,${Buffer.from(js + `\n//# sourceURL=${name}`).toString('base64')}`);
}
const baseline = await load(execFileSync('git', ['show', `${base}:${file}`], { encoding: 'utf8' }), 'before');
const patched = await load(fs.readFileSync(file, 'utf8'), 'after');
const text = 'export const answer = 42;\n'.repeat(11_000).slice(0, 256 * 1024);
const wire = '\x1b]52;c;' + Buffer.from(text).toString('base64') + '\x07';
const chunks = Array.from({ length: Math.ceil(wire.length / 4096) }, (_, i) => wire.slice(i * 4096, (i + 1) * 4096));
function run(parser) {
  let pending = '', output = '';
  for (const data of chunks) {
    const result = parser.sanitizeTerminalHistoryChunk(pending, data);
    pending = result.pendingControlSequence;
    output += result.visibleText;
  }
  assert.equal(pending, '');
  assert.equal(output, wire);
}
for (let i = 0; i < 2; i++) { run(baseline); run(patched); }
const samples = { baseline: [], patched: [] };
for (let i = 0; i < 7; i++) {
  const order = i % 2 ? [['patched', patched], ['baseline', baseline]] : [['baseline', baseline], ['patched', patched]];
  for (const [label, parser] of order) {
    const start = performance.now();
    run(parser);
    samples[label].push(performance.now() - start);
  }
}
const stats = values => {
  const sorted = values.toSorted((a, b) => a - b);
  return { minMs: sorted[0], medianMs: sorted[3], maxMs: sorted.at(-1), samples: values };
};
console.log(JSON.stringify({ node: process.version, decodedBytes: text.length, chunks: chunks.length, baseline: stats(samples.baseline), patched: stats(samples.patched) }, null, 2));
JS
```

</details>

