Bug Description:
In the Control UI (web dashboard), when the assistant starts executing a tool call (e.g., exec, browser, read, etc.), the user's previous messages disappear from the chat dialog. The messages are still stored correctly on the Gateway (verified by checking the session JSONL file), but they vanish from the UI during the tool execution phase.
Steps to Reproduce:
- Open Control UI at
http://127.0.0.1:18789/
- Send a message to the assistant
- Observe the assistant starts a tool call (tool execution indicator appears)
- Bug: The user's message disappears from the chat dialog
- After tool execution completes, the assistant's response appears, but the original user message remains missing
Expected Behavior:
All messages should remain visible in the chat dialog throughout the tool execution lifecycle.
Actual Behavior:
User messages disappear when tool calls are initiated. The messages are lost from the UI until a full page refresh or /new command is issued.
Root Cause Analysis (from source code review):
After analyzing dist/control-ui/assets/index-*.js, I identified three contributing issues:
1. RunId Mismatch in Ip() Function (Line ~109323)
function Ip(e,t){
if(t.runId && e.chatRunId && t.runId !== e.chatRunId){
if(t.state==="final"){
// handles final state
}
return null // <-- BUG: Non-final messages are silently dropped
}
}
When a tool call starts, it may generate events with a different runId or without a runId. The current logic returns null for any non-final message with mismatched runId, causing the message to be discarded from the UI.
2. Full History Replacement in Jn() Function (Line ~107153)
async function Jn(e){
const t = await e.client.request("chat.history", {...});
e.chatMessages = n.filter(s=>!As(s)); // <-- Full replacement
}
This function completely replaces chatMessages array instead of merging incrementally. If there's any race condition or partial load, messages can be lost.
3. Forced Refresh Trigger in nh() Function (Line ~122942)
function nh(e,t){
n==="final" && Hp(t) && Jn(e) // Triggers full history reload
}
When Hp(t) returns true (for non-assistant role messages), it triggers Jn(e) which reloads history. This can cause flickering or message loss.
Environment:
- OpenClaw version: Latest npm package (as of 2026-03-06)
- Browser: Any (Chrome, Firefox, Edge tested)
- OS: Windows 11 / macOS / Linux
- Gateway: Running locally on
127.0.0.1:18789
Workarounds:
- Refresh the page - Messages are preserved in Gateway, will reappear after reload
- Send
/new or /reset - Forces full history reload
- Switch tabs - Sometimes triggers re-render
Proposed Fixes:
Option 1: Fix RunId Handling (Recommended)
Modify Ip() function to append messages even when runId doesn't match, as long as they belong to the same session:
function Ip(e,t){
if(!t || t.sessionKey !== e.sessionKey) return null;
if(t.runId && e.chatRunId && t.runId !== e.chatRunId){
if(t.state === "final"){
// existing final state handling
}
// FIX: Don't drop messages for current session
if(t.sessionKey === e.sessionKey && t.state !== "error"){
const n = ir(t.message);
if(n && !As(n)){
e.chatMessages = [...e.chatMessages, n];
}
}
return null;
}
// ... rest of logic
}
Option 2: Incremental History Update
Modify Jn() to merge rather than replace:
async function Jn(e){
const t = await e.client.request("chat.history", {...});
const n = Array.isArray(t.messages) ? t.messages : [];
// FIX: Merge instead of replace
const existingIds = new Set(e.chatMessages.map(m => m.id || m.timestamp));
const newMessages = n.filter(s => !existingIds.has(s.id || s.timestamp));
if(newMessages.length > 0){
e.chatMessages = [...e.chatMessages, ...newMessages].slice(-200);
}
e.chatThinkingLevel = t.thinkingLevel ?? null;
}
Option 3: Local Message Cache (Defensive)
Store pending messages in localStorage before sending, restore on page load if missing from server response.
Impact:
- Severity: High - Makes Control UI difficult to use for multi-turn conversations
- User Experience: Users lose context of what they asked when tools are running
- Data Loss: No actual data loss (Gateway stores correctly), but UI appears broken
Additional Context:
The session JSONL file on Gateway correctly stores all messages. The issue is purely in the Control UI frontend's message rendering logic. This suggests a state management bug rather than a protocol or storage issue.
Labels: bug, ui, control-ui, high-priority
Bug Description:
In the Control UI (web dashboard), when the assistant starts executing a tool call (e.g.,
exec,browser,read, etc.), the user's previous messages disappear from the chat dialog. The messages are still stored correctly on the Gateway (verified by checking the session JSONL file), but they vanish from the UI during the tool execution phase.Steps to Reproduce:
http://127.0.0.1:18789/Expected Behavior:
All messages should remain visible in the chat dialog throughout the tool execution lifecycle.
Actual Behavior:
User messages disappear when tool calls are initiated. The messages are lost from the UI until a full page refresh or
/newcommand is issued.Root Cause Analysis (from source code review):
After analyzing
dist/control-ui/assets/index-*.js, I identified three contributing issues:1. RunId Mismatch in
Ip()Function (Line ~109323)When a tool call starts, it may generate events with a different
runIdor without arunId. The current logic returnsnullfor any non-final message with mismatchedrunId, causing the message to be discarded from the UI.2. Full History Replacement in
Jn()Function (Line ~107153)This function completely replaces
chatMessagesarray instead of merging incrementally. If there's any race condition or partial load, messages can be lost.3. Forced Refresh Trigger in
nh()Function (Line ~122942)When
Hp(t)returns true (for non-assistant role messages), it triggersJn(e)which reloads history. This can cause flickering or message loss.Environment:
127.0.0.1:18789Workarounds:
/newor/reset- Forces full history reloadProposed Fixes:
Option 1: Fix RunId Handling (Recommended)
Modify
Ip()function to append messages even whenrunIddoesn't match, as long as they belong to the same session:Option 2: Incremental History Update
Modify
Jn()to merge rather than replace:Option 3: Local Message Cache (Defensive)
Store pending messages in
localStoragebefore sending, restore on page load if missing from server response.Impact:
Additional Context:
The session JSONL file on Gateway correctly stores all messages. The issue is purely in the Control UI frontend's message rendering logic. This suggests a state management bug rather than a protocol or storage issue.
Labels:
bug,ui,control-ui,high-priority