Skip to content

[Bug]: TUI: "streaming" status desyncs from actual run state #69081

@EenvoudJasper

Description

@EenvoudJasper

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Two related bugs in the TUI's streaming-status handling cause the activity indicator to desync from the actual run state:

  1. False "idle" while the run is still active. When the agent is busy with tool calls and emits no chat delta events for 30s, the streaming watchdog fires and sets status to idle. The run is not actually idle — the gateway is still executing tools and will emit more deltas and a final event later.
  2. Stuck "streaming" forever after a missed final event. If the final chat event is lost (e.g. WebSocket drop without replay on reconnect), the TUI can sit on "streaming" indefinitely (observed: >1 hour). The 30s watchdog should catch this, but does not always fire in practice, and even when the final is lost the assistant output stays in the session store but is never rendered.

Root cause sketch

In dist/tui-CD9-89mO.js (~line 2720–2740, handleChatEvent):

if (evt.state === "delta") {
  const displayText = streamAssembler.ingestDelta(evt.runId, evt.message, state.showThinking);
  if (!displayText) return;
  chatLog.updateAssistant(displayText, evt.runId);
  setActivityStatus("streaming");
  if (state.activeChatRunId === evt.runId) armStreamingWatchdog(evt.runId);
}

The watchdog is only (re)armed on chat delta events. handleAgentEvent (~line 2802, stream === "tool") handles tool phases but never rearms the streaming watchdog. So a run that is actively executing tools looks dead to the watchdog once the last delta is 30s old — producing Bug 1.

For Bug 2, client.onConnected (~line 3950) triggers loadHistory() on reconnect but does not clear state.activeChatRunId or clearStreamingWatchdog(). If the WS dropped mid-run and the gateway finished while no subscriber was listening, the final event is lost (no backlog/replay in broadcastChatFinal). The TUI comes back up still believing activeChatRunId is set and watchdog state is stale.

Suggested fix (words, not code)

  • Bug 1: In handleAgentEvent, when a stream === "tool" event arrives for state.activeChatRunId, rearm the streaming watchdog (same armStreamingWatchdog(runId) call as the delta path). Tool activity is proof-of-life; the watchdog should track "gateway is silent about this run", not specifically "no chat deltas".
  • Bug 2: In client.onConnected, when reconnected === true, clear activeChatRunId, call clearStreamingWatchdog(), and set status to idle before loadHistory(). Any truly in-flight run will re-emit deltas and the TUI will re-adopt it; a run that finished during disconnect surfaces correctly via history reload instead of hanging.
  • Optional, larger: event replay on resubscribe so missed final events are delivered and finalizeAssistant still runs — touches the gateway broadcast path, not strictly necessary if history-reload on reconnect is acceptable.

Steps to reproduce

Bug 1 — watchdog fires during long tool calls

  1. Start openclaw tui.
  2. Send a prompt that causes the agent to do ≥30s of tool work without producing any assistant text between tools (e.g. several sequential read / exec grep calls).
  3. Wait 30 seconds after the last chat delta.

Bug 2 — stuck streaming, missed final

  1. Start openclaw tui and send a long-running prompt.
  2. Trigger a transient WebSocket disconnect during the run (on WSL2 / Tailscale this happens occasionally on its own; otherwise restart the gateway mid-run or briefly drop the network).
  3. Let the TUI reconnect automatically.

Expected behavior

  • Bug 1: While the gateway is actively processing a run — including tool-call phases without chat deltas — the TUI keeps the status indicator at streaming (or equivalent "working"). The watchdog only fires when the gateway is genuinely silent about the run for ≥30s across all event streams (chat + agent), not when it is just between deltas during tool work.
  • Bug 2: After a WebSocket reconnect the TUI does not remain in a stale streaming state. Either (a) the run is still in flight and new deltas re-establish it, or (b) the run finished during the disconnect and the reload of chat.history shows the final assistant output, with status back to idle. In no case should the user have to send a dummy message to recover the last output.

Actual behavior

  • Bug 1: After 30s of tool activity without chat deltas, the TUI logs streaming watchdog: no stream updates for 30s; resetting status. The backend may have dropped this run silently — send a new message to resync. and the status indicator switches to idle, even though the run is still executing server-side. When the run eventually finishes, the final output still arrives and is appended, but during the interval the user has no indication that the agent is still working.
  • Bug 2: The status indicator stays on streaming long after the gateway has finished (observed up to >1 hour). No streaming watchdog message appears. The final assistant text is missing from the chat buffer. Sending any new message triggers a chat.history reload which surfaces the missing output — confirming it was generated on the gateway but never delivered/rendered.

OpenClaw version

2026.4.15

Operating system

Ubuntu 24.04.3 LTS

Install method

npm install -g openclaw

Model

  • anthropic/claude-opus-4-7 (main agent), with anthropic/claude-sonnet-4-20250514 as default

Provider / routing chain

Direct Anthropic API for the models above (auth: anthropic:default, subscription/Max plan)

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

  • False-idle misleads the user into thinking the agent hung or stopped; they may re-send, creating duplicate runs.
  • False-streaming hides that output is already available; user waits indefinitely or manually resets the session.
  • Neither bug loses data on the gateway side — output is always in the session store — but the TUI doesn't surface it.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    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