Skip to content

[Bug]: TUI long messages vanish — fullRender(true) clears terminal scrollback (\x1b[3J) #78017

@joemcurry

Description

@joemcurry

Summary

Long assistant messages in openclaw tui appear briefly, then disappear from the screen entirely (including from terminal scrollback) once the conversation continues. Shorter messages render normally. Only reproduced via the TUI so far.

Environment

  • OpenClaw: 2026.5.4 (325df3e)
  • Interface: openclaw tui
  • pi-tui: bundled at node_modules/@mariozechner/pi-tui/dist/tui.js
  • OS: macOS (Darwin 25.3.0, arm64)

Symptom

  • Send/receive a long assistant message that overflows the visible viewport.
  • The full message is briefly visible while/just after streaming.
  • After the next render (subsequent message, status/footer change, focus event, etc.), the message vanishes — and is also gone from terminal scrollback (cannot scroll up to recover it).
  • Short messages are unaffected.

Likely root cause

pi-tui uses normal-screen rendering plus a differential-redraw path that occasionally falls back to fullRender(true). fullRender(true) issues:

\x1b[?2026h          (begin sync)
\x1b[2J\x1b[H\x1b[3J (clear screen + home + clear scrollback)
... rewrites only current `newLines` ...
\x1b[?2026l

The third escape (\x1b[3J) wipes the terminal’s scrollback buffer. After that, only the bottom terminal_height lines of newLines remain visible — anything that scrolled into scrollback during prior renders is gone.

There are three triggers for fullRender(true) in pi-tui/dist/tui.js:

  1. widthChanged — line ~743
  2. heightChanged (non-Termux) — line ~751
  3. firstChanged < prevViewportTop — line ~842

(3) is the suspected culprit for this report. While a long assistant message streams, AssistantMessageComponentHyperlinkMarkdownMarkdown.setText() invalidates and re-renders the entire markdown body on each chunk. As the body grows past the viewport, any changed line in the early portion of the rendered buffer (e.g., a line that re-flows due to a paragraph boundary shift, a blockquote close, or a code-fence open) sets firstChanged to a position above prevViewportTop. That triggers fullRender(true), clearing scrollback and leaving only the last height lines visible.

Code references (paths within bundled openclaw install):

  • node_modules/@mariozechner/pi-tui/dist/tui.js
    • \x1b[2J\x1b[H\x1b[3J in fullRender (within doRender)
    • if (firstChanged < prevViewportTop) { fullRender(true); return; }
  • dist/tui-CD7AaN2H.js
    • AssistantMessageComponent.setTextHyperlinkMarkdown.setTextMarkdown.setTextinvalidate()

Why it’s surprising

pi-tui deliberately uses normal-screen + scrollback (not alt-screen) so that conversation history remains scrollable in the user’s terminal. \x1b[3J in the fullRender path defeats that goal whenever a non-resize trigger fires — the user permanently loses the rendered content above the viewport.

Suggested fixes

Pick one or combine:

  1. Drop \x1b[3J from the firstChanged < prevViewportTop path. Width/height changes plausibly justify clearing scrollback (wrapping changes), but a content-change full-redraw doesn’t. For path (3), a redraw of just the visible viewport is sufficient.
  2. Stabilize early lines during streaming. When only the trailing portion of a streaming markdown changes, ensure earlier lines are byte-identical to the previous render so firstChanged lands inside the viewport.
  3. Treat scroll-pinned content above viewport as immutable. If firstChanged < prevViewportTop, accept that scrollback is authoritative for those lines and only redraw from prevViewportTop downward.

Repro

  1. Start openclaw tui in any terminal that supports \x1b[3J (iTerm2, Terminal.app, kitty, alacritty, Ghostty all do).
  2. Ask a question that produces an assistant reply longer than the visible terminal height (e.g., “explain X in detail with examples and a code block”).
  3. Wait for the reply to finish streaming, then send any follow-up — or trigger any event that re-renders (resize back-and-forth, focus change, status update).
  4. Try to scroll up in the terminal. The long message is gone.

Diagnostic capture

For maintainers reproducing locally:

PI_DEBUG_REDRAW=1 openclaw tui
# tail -f ~/.pi/agent/pi-debug.log

Each fullRender logs its reason (first render, terminal width changed, terminal height changed, firstChanged < viewportTop, clearOnShrink, extraLines > height, deleted lines moved viewport up). The expected log line for this bug is:

fullRender: firstChanged < viewportTop (<n> < <m>) (prev=<X>, new=<Y>, height=<H>)

Related

Impact

UX bug — content the user already saw cannot be recovered, including via the terminal’s native scrollback. Long-form responses (explanations, code review output, plan documents) are the most affected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions