Attaching to a sprites box renders correctly at first, then corrupts once content scrolls near the bottom: the status band is drawn twice at different rows, and band text ends up interleaved into a content line mid-screen.
Not a sizing bug
Ruled out by measurement on a live box:
- terminal size propagates: host 143 cols produced tmux
pane=143x46 (one row for the band)
- SIGWINCH propagates: host resized 30x100 to 55x150, remote
stty size reported 30x100 then 55x150
- the transport does not split the stream across stdout/stderr: stderr carried 0 bytes in TTY mode
What is actually happening
wrapped-pty/run.ts:969 restricts the OUTER terminal to rows 1..innerRows so the inner program cannot scroll into the footer, and re-asserts it on relayout/resize (:460, :789, :1095).
But tmux inside the box emits its own ESC[N;Mr constantly. Captured from a real attach — 14 seconds, tmux running seq 1 500:
total bytes = 4601
ESC[r (region reset) = 0
ESC[N;Mr (region set) = 57
ESC[?1049 (alt screen) = 1
57 scroll-region writes in 14s, all landing on the same outer terminal as the wrapper band.
That is normally harmless: the inner PTY is sized to innerRows, so tmux computes regions inside that height and never addresses the band row. The problem is the resize window. When the alert band appears or disappears, reservedRows() changes, the wrapper moves its band and resizes the inner pty — and the remote repaints at the OLD size until the resize lands. On docker that window is microseconds. Over a network transport it is a round trip, and tmux paints into rows that are now band rows.
Hence: fine until something scrolls or the band changes, then band text stranded in content and a doubled band.
Scope
The wrapper assumption — that an inner resize takes effect before the next paint — is shared, so hetzner/daytona/remote-docker are exposed too; sprites just has enough latency to hit it reliably. Reproduces with agentbox claude --provider sprites in a repo whose agent output scrolls.
Possible directions
- Repaint/clear the reserved rows after a relayout rather than only redrawing the band, so a stale remote paint is overwritten rather than left behind.
- Defer the band move until the inner pty has produced output at the new size.
- Re-assert the scroll region on a short timer while a resize is in flight.
Found while adding the sprites provider. The provider-side sizing (SDK PTY bridge with spawn({tty,cols,rows}) + resize()) is fixed and verified; this is the remaining wrapper-side piece.
Attaching to a sprites box renders correctly at first, then corrupts once content scrolls near the bottom: the status band is drawn twice at different rows, and band text ends up interleaved into a content line mid-screen.
Not a sizing bug
Ruled out by measurement on a live box:
pane=143x46(one row for the band)stty sizereported30x100then55x150What is actually happening
wrapped-pty/run.ts:969restricts the OUTER terminal to rows1..innerRowsso the inner program cannot scroll into the footer, and re-asserts it on relayout/resize (:460,:789,:1095).But tmux inside the box emits its own
ESC[N;Mrconstantly. Captured from a real attach — 14 seconds, tmux runningseq 1 500:57 scroll-region writes in 14s, all landing on the same outer terminal as the wrapper band.
That is normally harmless: the inner PTY is sized to
innerRows, so tmux computes regions inside that height and never addresses the band row. The problem is the resize window. When the alert band appears or disappears,reservedRows()changes, the wrapper moves its band and resizes the inner pty — and the remote repaints at the OLD size until the resize lands. On docker that window is microseconds. Over a network transport it is a round trip, and tmux paints into rows that are now band rows.Hence: fine until something scrolls or the band changes, then band text stranded in content and a doubled band.
Scope
The wrapper assumption — that an inner resize takes effect before the next paint — is shared, so hetzner/daytona/remote-docker are exposed too; sprites just has enough latency to hit it reliably. Reproduces with
agentbox claude --provider spritesin a repo whose agent output scrolls.Possible directions
Found while adding the sprites provider. The provider-side sizing (SDK PTY bridge with
spawn({tty,cols,rows})+resize()) is fixed and verified; this is the remaining wrapper-side piece.