Problem
The shellper's replay buffer is bounded by line count (10,000 lines) plus an unbounded whole-kept partial. A full-screen TUI (Claude sessions) redraws in place via cursor addressing and emits almost no newlines, so an entire multi-day session accumulates as one line that never ends: the line cap never triggers and the buffer grows monotonically for the life of the session.
Measured impact (from the #1198 incident, 2026-07-19):
Byte-trimming the buffer from the front was deliberately rejected previously (#1047): a trim can cut mid-escape-sequence or drop the alt-screen-enter, corrupting replay rendering. So the buffer is whole-by-design, and its growth is unbounded-by-design — but the wire (16MB frame cap) and the UI (xterm parse cost per attach) are not.
Containment already in place (PR #1204)
These make the current design safe, not right:
- Tower's frame parser discards oversized frames (
'frame-skipped') instead of treating them as fatal stream errors.
- New shellpers truncate the replay they send to an 8MB tail (
REPLAY_PAYLOAD_MAX).
- Adoption seeds at most a 1MB tail into the ring buffer (
RING_SEED_MAX_BYTES); viewers rely on the post-connect resize nudge to repaint TUIs.
Every cap is a lossy tail-cut that can transiently render rough until the nudge repaints. The buffer itself still grows without bound in shellper memory.
Proposed direction
Make replay O(screen), not O(session lifetime): store something screen-shaped rather than stream-shaped.
- Preferred: run PTY output through a headless terminal-state emulator inside the shellper and serve reconnecting clients the current screen state (plus bounded recent scrollback). Replay size becomes a few hundred KB at any session age, with no escape-sequence corruption risk, and the resize-nudge crutch becomes unnecessary.
- Cheaper alternative: reset the buffer at detectable full-frame boundaries (clear-screen / cursor-home redraw sequences), keeping only bytes since the last full repaint.
Wire-compat constraint: running shellpers are old binaries that survive Tower upgrades, so Tower must keep tolerating old-style (potentially oversized) replays regardless.
Acceptance criteria
Refs: #1198, PR #1204.
Problem
The shellper's replay buffer is bounded by line count (10,000 lines) plus an unbounded whole-kept partial. A full-screen TUI (Claude sessions) redraws in place via cursor addressing and emits almost no newlines, so an entire multi-day session accumulates as one line that never ends: the line cap never triggers and the buffer grows monotonically for the life of the session.
Measured impact (from the #1198 incident, 2026-07-19):
MAX_FRAME_SIZEwire cap. The resulting oversized REPLAY frame deterministically errored Tower's parser on every reconnect. On pre-Shellper reconnect error is swallowed: terminal becomes a silent zombie (no input/output, 'Message sent' logged for dropped frames) until next Tower restart #1198 code that swallowed error minted the original silent-zombie terminals; during the Shellper reconnect error is swallowed: terminal becomes a silent zombie (no input/output, 'Message sent' logged for dropped frames) until next Tower restart #1198 rollout it escalated to two killed sessions before the tolerant-parser fix landed.Terminal partial monitor: max partial 14081 KB).Byte-trimming the buffer from the front was deliberately rejected previously (#1047): a trim can cut mid-escape-sequence or drop the alt-screen-enter, corrupting replay rendering. So the buffer is whole-by-design, and its growth is unbounded-by-design — but the wire (16MB frame cap) and the UI (xterm parse cost per attach) are not.
Containment already in place (PR #1204)
These make the current design safe, not right:
'frame-skipped') instead of treating them as fatal stream errors.REPLAY_PAYLOAD_MAX).RING_SEED_MAX_BYTES); viewers rely on the post-connect resize nudge to repaint TUIs.Every cap is a lossy tail-cut that can transiently render rough until the nudge repaints. The buffer itself still grows without bound in shellper memory.
Proposed direction
Make replay O(screen), not O(session lifetime): store something screen-shaped rather than stream-shaped.
Wire-compat constraint: running shellpers are old binaries that survive Tower upgrades, so Tower must keep tolerating old-style (potentially oversized) replays regardless.
Acceptance criteria
MAX_FRAME_SIZE) regardless of session age.Refs: #1198, PR #1204.