Files
d04b05b5c8 Detach retained CI and terminal tails from oversized strings (#20960)
* fix(memory): detach retained CI and terminal tails from oversized strings

* fix(terminal): detach retained error and reattach string slices

* fix(terminal): release oversized recent-output backing strings

* fix(terminal): release backing strings held by PTY detectors

* fix(memory): own bounded Claude background task labels

* fix: detach retained terminal mode scan tails

* fix: own retained plugin worker output strings

* fix: own incomplete OSC 133 carry strings

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
2026-09-17 20:34:19 -07:00
..

Retained OSC 133 incomplete carry

The shared command-lifecycle scanner keeps an incomplete OSC 133 suffix of at most 4,096 UTF-16 code units. A V8 sliced string can keep the entire preceding PTY input alive through that small suffix. The correction copies only the final incomplete carry through existing ownRetainedString; short prefixes, content, parsing, callbacks, authority and reset behavior are preserved.

This adds the fifteenth retained-text boundary to #20960, following the kitty/mouse tails and other retained text slices. It introduces no wire change and applies equally to local and SSH/remote terminal bytes reaching the shared scanner.

Reproduce

ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/osc133-carry-retention/reproduce.cjs

Run the same script with the installed Electron executable, setting ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, with the same Node flags. No application window, native PTY, or network is created. The runner has a 60-second deadline and accepts an optional output-report path as its first argument; otherwise it writes Node or Electron results here.

The portable loader validates all 28 bundled source modules and seven additional caller/fixture files. Non-evaluated provenance callers accept only the recorded audited or named-main bytes, and reports identify which was present; evaluated modules each require one exact fixed hash. It reverses only the new import/copy call in memory through a zero-context patch, then validates the baseline hash. All evaluated-source and artifact hashes are recorded. It needs no Git history, ignored notes, or absolute developer paths. Source/patch reads normalize CRLF; an in-memory CRLF control checks equivalent before/after strings.

The scanner baseline exactly matches named main 291b4ddd6f1c1af480169885e0fda7f9c78ff053 and v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43). The copier did not exist in v1.4.198; current helpers and callers are used for both sides of this experiment. Source provenance records each named identity/absence separately. This is not a replay of a complete historical app.

Result and controls

Both Node 26.6 and Electron 43.7 / Node 24.21 pass 117 cases. Each runtime compares the baseline, fixed Buffer copier and fixed Bufferless copier through the actual scanner, shared title tracker, and daemon background transient-fact relay. Thirty-two 64 Ki-character inputs retain roughly 2 MiB before the copy; eight 1 Mi-character inputs retain roughly 8 MiB. Fixed deltas are below the asserted 1 MiB tolerance, including owner overhead. Completion and reset/exit release the old parents. Exact GC-sensitive measurements are in the reports; they are heap deltas, not RSS or exact allocation attribution.

The ordinary sequence bytes come from the fish 4.7.1 capture documented in src/shared/terminal-mode-2031-final-state.test.ts: A;click_events=1 and C;cmdline_url=npx. That capture contains complete OSC sequences. The large plain-output prefix and cut before the terminator are synthetic. This does not claim the original capture had those sizes or boundaries.

Controls preserve BEL/ST completion, split prefixes, C/D callback values, background disable/re-enable, reset, and every split of a Unicode/NUL/lone- surrogate fixture. The Bufferless copier is selected while Buffer is absent, then Buffer is restored before measurement; this exercises the renderer's actual fallback without launching a renderer. Short ordinary D;0 prefixes, complete sequences and plain input are negative retention controls. Oversized unterminated input is separately labelled malformed-protocol stress. V8's independent last successful RegExp input is reset before both measurements.

Eight permanent tests cover both copier paths, long captured-fish suffixes, completion, reset and short D;0. With the in-memory baseline overlay, exactly four long-suffix regressions fail at 33,550,680–33,565,360 retained bytes against a 2 MiB allowance; the other 41 tests in the four-suite run pass. The fixed run passes all 45. Wider proof/quality validation is recorded in validation.json.

Owners and ordinary input bounds

Main creates a per-PTY tracker with onCommandFinished in orca-runtime-get-unpersisted-tracked-title-for-pty.ts; scanner enablement still respects transient-fact consumer/authority state. Ordinary daemon output frames delivered to main are sliced to 64 Ki characters in daemon-stream-data-batcher.ts, and ordinary relay output to 16 Ki characters in src/relay/pty-handler.ts. The 64 Ki cases therefore demonstrate retention without requiring a multi-megabyte main input; the 1 Mi cases amplify the mechanism. Replay and transformed output have their own existing limits.

The daemon's BackgroundTransientFactRelay owns one tracker per background session. daemon-terminal-admission.ts feeds it before output batching, so the batcher's later slicing is not an input cap on this daemon scanner. Native data passes through pty-subprocess/subprocess-handle.ts, the session's shell readiness/startup/recovery path, and its stream client. The inspected local intake does not impose an independent string-length limit; platform/native library chunk sizes were not measured here.

Completion/replacement of the incomplete escape, scanner reset, session exit, background retirement, tracker disposal or owner release drops the old parent. This is at most the last incomplete-parent cost per live scanner, not a list of every historical chunk. Multiple readers may share the same input backing storage; do not add their isolated measurements as independent process totals.

This is a reproduced code-level retention mechanism. It does not establish a native output pause, normal-session frequency/duration, the trigger in #19831/#19768, a reported sustained growth rate, or a multi-gigabyte incident's cause. The change does not reduce original input allocation.