Skip to content

Control UI: Messages disappear during tool execution #37083

@fangkelvin

Description

@fangkelvin

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:

  1. Open Control UI at http://127.0.0.1:18789/
  2. Send a message to the assistant
  3. Observe the assistant starts a tool call (tool execution indicator appears)
  4. Bug: The user's message disappears from the chat dialog
  5. 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:

  1. Refresh the page - Messages are preserved in Gateway, will reappear after reload
  2. Send /new or /reset - Forces full history reload
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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