[Bug] WebChat: optimistic USER messages are lost (not just hidden) when chat.send returns before the durable transcript lands — full-replace history reconcile drops them
OpenClaw version: 2026.5.22 (a374c3a)
Component: Control UI / WebChat (ui/src/ui/controllers/chat.ts, ui/src/ui/app-gateway.ts)
Severity: High — silent message loss on the primary control surface (not merely a display glitch)
Summary
A user message submitted in WebChat is shown optimistically, then disappears ~1 minute later and is never processed by the agent. Unlike the previously-fixed assistant/streaming cases, here the user message is lost: it never reaches a durable session transcript and no agent turn ever runs for it, so the user believes they sent something and the agent simply never replies.
This persists on 2026.5.22 after the incremental-history-merge work in #66332 (closed COMPLETED) — that fix addressed the assistant/streaming full-replace race, but the optimistic user-send path is still vulnerable.
Root cause (from runtime behavior)
- WebChat appends the user message optimistically to
chatMessages and calls chat.send.
chat.send returns "started" quickly (well before the user message is durably written into the session transcript that chat.history reads from).
- A subsequent
chat.history reload full-replaces chatMessages with canonical history. Because the durable user transcript hasn't caught up, the optimistic user bubble has no match in the returned history and is dropped.
- The send did not result in a queued/persisted turn, so the agent never processes the message.
This is the same full-replace reconcile race described in the #66316 / #66332 / #83949 cluster, but for user messages on the send path rather than assistant messages on the terminal-event path.
Reproduction (live, captured on 2026.5.22)
- Open WebChat, send a message.
- Gateway logs:
chat.send accepted and returns in ~200ms:
[ws] ⇄ res ✓ chat.send 198ms runId=c13027d4-686c-459f-beef-f371d0af99ab conn=… id=…
- The optimistic bubble shows, then vanishes within ~1 min on the next
chat.history reconcile.
- The runId never appears in any session transcript under
…/agents/<agent>/sessions/*.jsonl, and no agent turn / turn-maintenance runs for it — i.e. the
message was genuinely lost, not just hidden. (In our capture the only subsequent activity in the
session was an unrelated scheduled heartbeat turn.)
Reproducibility is timing-dependent (the gap between chat.send "started" and the durable transcript write); it shows up reliably when the gateway is busy (e.g. context-engine compaction running, which our logs show firing right after the send).
Expected behavior
A submitted user message must never be silently lost. Either:
- the optimistic user bubble must be preserved across
chat.history reconcile until the durable
transcript confirms it, and/or
chat.send must durably persist the user message before signaling, so the subsequent history
reload includes it.
Known-good local fix (happy to PR)
We patched the Control UI to track pending local user messages keyed by runId/idempotencyKey,
tag the optimistic bubble with metadata, and preserve pending bubbles through chat.history
reconciliation, clearing them only when canonical history includes the durable user message or when
chat.send fails. This resolves the loss in our environment. Conceptually it's the user-message analog
of the incremental-merge approach from #66332. We can open a PR if useful.
Related
[Bug] WebChat: optimistic USER messages are lost (not just hidden) when
chat.sendreturns before the durable transcript lands — full-replace history reconcile drops themOpenClaw version: 2026.5.22 (
a374c3a)Component: Control UI / WebChat (
ui/src/ui/controllers/chat.ts,ui/src/ui/app-gateway.ts)Severity: High — silent message loss on the primary control surface (not merely a display glitch)
Summary
A user message submitted in WebChat is shown optimistically, then disappears ~1 minute later and is never processed by the agent. Unlike the previously-fixed assistant/streaming cases, here the user message is lost: it never reaches a durable session transcript and no agent turn ever runs for it, so the user believes they sent something and the agent simply never replies.
This persists on 2026.5.22 after the incremental-history-merge work in #66332 (closed COMPLETED) — that fix addressed the assistant/streaming full-replace race, but the optimistic user-send path is still vulnerable.
Root cause (from runtime behavior)
chatMessagesand callschat.send.chat.sendreturns"started"quickly (well before the user message is durably written into the session transcript thatchat.historyreads from).chat.historyreload full-replaceschatMessageswith canonical history. Because the durable user transcript hasn't caught up, the optimistic user bubble has no match in the returned history and is dropped.This is the same full-replace reconcile race described in the #66316 / #66332 / #83949 cluster, but for user messages on the send path rather than assistant messages on the terminal-event path.
Reproduction (live, captured on 2026.5.22)
chat.sendaccepted and returns in ~200ms:chat.historyreconcile.…/agents/<agent>/sessions/*.jsonl, and no agent turn / turn-maintenance runs for it — i.e. themessage was genuinely lost, not just hidden. (In our capture the only subsequent activity in the
session was an unrelated scheduled heartbeat turn.)
Reproducibility is timing-dependent (the gap between
chat.send"started" and the durable transcript write); it shows up reliably when the gateway is busy (e.g. context-engine compaction running, which our logs show firing right after the send).Expected behavior
A submitted user message must never be silently lost. Either:
chat.historyreconcile until the durabletranscript confirms it, and/or
chat.sendmust durably persist the user message before signaling, so the subsequent historyreload includes it.
Known-good local fix (happy to PR)
We patched the Control UI to track pending local user messages keyed by
runId/idempotencyKey,tag the optimistic bubble with metadata, and preserve pending bubbles through
chat.historyreconciliation, clearing them only when canonical history includes the durable user message or when
chat.sendfails. This resolves the loss in our environment. Conceptually it's the user-message analogof the incremental-merge approach from #66332. We can open a PR if useful.
Related