Metadata
| Status | done |
|---|---|
| Assigned | agent-1038 |
| Agent identity | f51439356729d112a6c404803d88015d5b44832c6c584c62b96732b63c2b0c7e |
| Model | claude:opus |
| Created | 2026-04-29T00:23:23.137908711+00:00 |
| Started | 2026-04-29T00:31:08.791962346+00:00 |
| Completed | 2026-04-29T00:40:14.034476444+00:00 |
| Tags | bug,tui,pty, eval-scheduled |
| Eval score | 0.93 |
| └ blocking impact | 0.92 |
| └ completeness | 0.98 |
| └ constraint fidelity | 0.85 |
| └ coordination overhead | 0.90 |
| └ correctness | 0.95 |
| └ downstream usability | 0.88 |
| └ efficiency | 0.85 |
| └ intent fidelity | 0.75 |
| └ style adherence | 0.95 |
Description
Description
User reports: in the wg TUI, the PTY scrollback buffer redraws when the PTY window is resized. After resize, the last chunk of the scrollback is sometimes duplicated — i.e., the most recent N lines appear twice in the rendered scrollback area.
User quote: 'the scroll back buffer in the PTY, if you change the dimensions of the PTY window, it can redraw. But it seems that sometimes it ends up not redrawing the last part properly. You get like a duplication of the last chunk. ... Basically duplication is a thing.'
Repro path (start here)
- Open the wg TUI
- Run a command in a chat / spawn output that fills more than one screen of scrollback
- Resize the TUI window (drag terminal corner, or send a SIGWINCH equivalent)
- Scroll back through the buffer
- Observe: the last chunk (last screen-worth-ish of lines) appears duplicated
Goal
Reproduce + identify root cause. Likely candidates:
- The redraw path is appending the visible region a second time instead of replacing it
- Wrap-rewrap logic on resize miscomputes the new line count and emits trailing lines that overlap with the existing tail
- The scrollback buffer's tail pointer isn't updated after re-wrap, so subsequent renders read past the real end
- Cursor / last-line position is not snapshotted before resize, so the post-resize render duplicates whatever was 'below' the old cursor
Files likely involved (start here, do NOT chase widely):
- src/tui/ — anything mentioning pty / scrollback / resize / SIGWINCH
- Recent commits in fix-pty-output, tui-live-log, tui-chat-bar may have touched this code path
Validation
-
Reproduction confirmed and described step-by-step in the task log (
wg log) - Root cause identified — specific file + function + line range, with explanation of why duplication happens
- If the bug is intermittent: identify the trigger condition (specific resize direction? specific buffer fill state?) so the fix task can write a deterministic test
- Recommended fix approach noted (e.g., 'snapshot tail pointer before re-wrap, restore after' or 'replace render-append with render-replace')
- No source modifications — diagnose only
Depends on
Required by
Log
- 2026-04-29T00:23:23.123216325+00:00 Task paused
- 2026-04-29T00:23:45.779551059+00:00 Task published
- 2026-04-29T00:24:07.669152632+00:00 Lightweight assignment: agent=Careful Programmer (f5143935), exec_mode=light, context_scope=task, reason=Diagnosis of TUI PTY scrollback bug requires systematic code investigation with careful attention to precision; Careful Programmer's track record (0.76 score, 240 tasks) and systematic debugging approach are ideal for identifying root cause in complex buffer management logic.
- 2026-04-29T00:31:08.791965773+00:00 Spawned by coordinator --executor claude --model opus
- 2026-04-29T00:31:15.158755467+00:00 Starting diagnosis: locating PTY scrollback + resize handling code in src/tui/
- 2026-04-29T00:34:58.765101164+00:00 Read pty_pane.rs and vt100 0.16.2 set_size. Existing tests verify visible-region after parser set_size, but NOT (a) scrollback content after resize or (b) child re-printing on SIGWINCH. Building a standalone reproducer.
- 2026-04-29T00:39:05.595282709+00:00 REPRODUCED via standalone vt100-only reproducer at /tmp/wg-pty-repro/. Three duplication variants confirmed: (TEST2) child re-prints last N lines after SIGWINCH; (TEST3) child does \e[2J\e[H + full repaint (canonical TUI reflow — most likely actual cause for claude/codex); (TEST4) race ordering where child writes BEFORE PtyPane::resize calls parser.set_size. Pure parser.set_size() with no child reprint does NOT duplicate (TEST1 PASS), so vt100::set_size itself is innocent.
- 2026-04-29T00:39:12.679806769+00:00 ROOT CAUSE: PtyPane::resize (src/tui/pty_pane.rs:435-466) calls master.resize FIRST, sending SIGWINCH to the embedded TUI child (claude/codex/ink-React). The child reflows by emitting \e[2J\e[H + repainting every visible row. Reader thread (src/tui/pty_pane.rs:259-261) feeds those bytes into the shared vt100::Parser. When the cumulative cursor advance exceeds the new visible row count, vt100::Grid::scroll_up (vt100-0.16.2 grid.rs:566-573) pushes the row at the TOP of the visible region into scrollback — but that exact content already lives in scrollback (was visible just before the resize). Result: last screen-or-so worth of rows appears TWICE in scrollback when the user scrolls back.
- 2026-04-29T00:39:31.883330361+00:00 WHY EXISTING TESTS MISS IT: render_after_resize_does_not_double_lines (src/tui/pty_pane.rs:1281-1316) feeds bytes ONCE before set_size, never simulates the child's SIGWINCH-driven reflow (no second batch of process() after set_size). Also render_to_text (line 1050) renders only at scrollback_offset=0 — never inspects scrollback rows. The five doubling-tests added in 572a28d37 all target streaming-vs-finalized chat-tab rendering, none exercise the real-PTY child-reflow path.
- 2026-04-29T00:39:38.093726746+00:00 TRIGGER CONDITIONS (deterministic): (a) visible region has filled at least once so scrollback is non-empty; (b) PTY pane resizes — any direction, grow or shrink; (c) child writes content whose cursor advance during reflow exceeds the new visible row count. (c) is satisfied by ANY full-screen TUI repaint, which is exactly what claude/codex emit on SIGWINCH. Width changes that force re-wrapping make (c) more likely (more rows of output for the same content).
- 2026-04-29T00:39:43.578642908+00:00 RECOMMENDED FIX (preferred): snapshot-and-restore around the SIGWINCH window. In PtyPane::resize, before master.resize: record scrollback_len_before AND the bottom-N visible rows (N = old rows). After a quiet window (~50-100ms of no new bytes from reader thread), inspect scrollback entries appended after scrollback_len_before — if they prefix-match the pre-resize visible bottom-N, drop them. Preserves real new content; elides the SIGWINCH reflow echo. Simpler alternative: zero the scrollback cap during a 100ms post-resize window then restore (loses any genuinely new content during the window but trivially correct).
- 2026-04-29T00:39:48.028318848+00:00 SECONDARY ISSUE FOUND (not the user's report but worth noting for fix-tui-pty): vt100::Grid::set_size shrinks self.rows via VecDeque::resize which truncates from the END (drops bottom rows). It does NOT push dropped rows into scrollback. So shrinking the TUI pane vertically silently LOSES bottom-row content. Phase 5 of the reproducer confirms: shrinking a 10-row parser holding 6 lines down to 4 rows discards rows 4-5; scrollback stays empty (offset=0, max=0). Data loss, not duplication — different bug. Mention to fix task so they can either tackle in same PR or add a follow-up.
- 2026-04-29T00:39:54.380587883+00:00 REPRO ARTIFACT: standalone reproducer at /tmp/wg-pty-repro/ (Cargo.toml + src/main.rs, depends on vt100 = 0.16). Cargo run prints PASS/DUP per scenario. Detailed writeup at /tmp/diagnose-tui-pty-findings.md (the fix-tui-pty agent should read both).
- 2026-04-29T00:40:14.034488076+00:00 Task pending eval (agent reported done; awaiting `.evaluate-*` to score)
- 2026-04-29T00:43:40.431555014+00:00 PendingEval → Done (evaluator passed; downstream unblocks)