-
-
Notifications
You must be signed in to change notification settings - Fork 79.1k
[TUI] Final assistant message disappears on completion — loadHistory() clearAll() races server persistence (not a repaint bug; #86871 / #87423 does not fix it) #87922
Copy link
Copy link
Closed
Labels
P1High-priority user-facing bug, regression, or broken workflow.High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.ClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.Channel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Metadata
Metadata
Assignees
Labels
P1High-priority user-facing bug, regression, or broken workflow.High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.ClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.Channel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Type
Fields
Give feedbackNo fields configured for issues without a type.
Summary
The last assistant message intermittently disappears from the TUI right as a run finishes streaming.
/verbose off(or any history reload) brings it back. This was previously assumed to be a repaint problem (#86871, fixed by #87423 forcingrequestRender(true)), but that fix does not resolve it. The real cause is a read-after-write race: on everyfinalevent for an external/gateway run, the TUI firesloadHistory(), which doeschatLog.clearAll()and rebuilds the log from the server response. If the gateway hasn't persisted the just-finished message yet, the rebuilt log drops it.This is a separate bug from #86871. Forcing a repaint cannot fix it because the local render was already correct — the reload replaces correct local content with stale server content.
Why #86871 / #87423 does not fix it
#87423 made the three
finalearly-returns inhandleChatEventcallrequestRender(true). But the disappearing message has already been rendered correctly at finalize time. The content is then wiped by an asyncloadHistory()that resolves later. No repaint flag affects this — verified on an install whose bundle already forcesrequestRender(true)across all ofhandleChatEvent; the bug still reproduces.Root cause
On a normal
finalfor a non-local run,handleChatEvent(src/tui/tui-event-handlers.ts, ~line 514) does:loadHistory(src/tui/tui-session-actions.ts, ~line 297):Sequence
finalevent →maybeRefreshHistoryForRunfiresvoid loadHistory()(async fetch starts).finalizeAssistant(finalText)+requestRender(true)→ message renders; user sees it.loadHistoryfetch resolves →clearAll()wipes everything, rebuilds fromrecord.messages.loadHistoryfetched, i.e. thefinalstream event reached the TUI before the message was written to history, sorecord.messagesomitted it./verbose off→loadHistory()again → server now has it → message reappears.Intermittent because it depends on persistence-vs-fetch timing.
Why external/gateway runs only
Local runs are guarded (
src/tui/tui-event-handlers.ts, ~lines 351-356):External/gateway runs have no equivalent guard, so they reload history on every
finaland are always exposed to the race. Reproduces on agent/gateway sessions (e.g.agent:main:...).Evidence
pi-tuiredraw log (~/.pi/agent/pi-debug.log,PI_DEBUG_REDRAW=1) at the moment of a vanish:The render input collapsed from 190 to 8 lines — i.e. the content was removed from the chat log before rendering, not mis-painted.
Ruled out
streamAssembler.finalize()returning"(no output)"→ no:resolveFinalAssistantText(src/tui/tui-formatters.ts:213) falls back tostreamedText, which is non-empty after a stream, sodropAssistantvia the"(no output)"path is not taken.Suggested fix directions
Not a repaint fix — avoid destroying correct local content:
record.messages, re-add it (detect-missing-and-restore).finalizealready produced displayable text, skip the reload (avoid the destructiveclearAll()).clearAll()(merge rather than wipe).Relation to #86871 / #87423
This supersedes the assumption in #86871. #87423 (
requestRender(true)) is a valid repaint hardening but does not fix the disappearing-message symptom; the cause is theloadHistory()reload racing server persistence, not the repaint flag.Environment
requestRender(true)throughouthandleChatEvent.