Skip to content

Control UI: avoid chat flicker on session reload#66271

Closed
Schnitzel wants to merge 3 commits intoopenclaw:mainfrom
Schnitzel:fix/control-ui-chat-flicker-66207
Closed

Control UI: avoid chat flicker on session reload#66271
Schnitzel wants to merge 3 commits intoopenclaw:mainfrom
Schnitzel:fix/control-ui-chat-flicker-66207

Conversation

@Schnitzel
Copy link
Copy Markdown

Summary

  • Problem: Control UI user messages can disappear briefly after send in 2026.4.12.
  • Why it matters: the chat transcript visibly flickers on every send, which makes the UI feel unreliable.
  • What changed: skip the session.message-triggered history reload when it is only echoing the active run's own optimistic user message, and add a regression test.
  • What did NOT change (scope boundary): no broader optimistic-message reconciliation or transcript merge behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: 2026.4.12 added a Control UI session.message handler that unconditionally reloaded chat.history for the active session. The chat UI already renders the user's message optimistically, and loadChatHistory replaces chatMessages wholesale, so an early reload can briefly swap in a stale transcript snapshot before persistence catches up.
  • Missing detection / guardrail: there was no regression test covering an active optimistic user message during a session.message echo.
  • Contributing context (if known): 4.11 did not have this session.message reload path in Control UI.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: ui/src/ui/app-gateway.sessions.node.test.ts
  • Scenario the test should lock in: while chatRunId is active, a same-session session.message event carrying a user message should not trigger loadChatHistory.
  • Why this is the smallest reliable guardrail: the regression was introduced in the Control UI gateway event handler, so that seam isolates the behavior directly.
  • Existing test that already covers this (if any): none
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Just-sent user messages remain visible continuously instead of disappearing briefly before transcript persistence catches up.

Diagram (if applicable)

Before:
[user send] -> [optimistic user message visible] -> [session.message reloads history] -> [stale history replaces optimistic row] -> [later history reload restores message]

After:
[user send] -> [optimistic user message visible] -> [session.message user echo is ignored during active run] -> [message stays visible]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local UI test runner
  • Model/provider: N/A
  • Integration/channel (if any): Control UI webchat
  • Relevant config (redacted): default local dev config

Steps

  1. Start from the Control UI chat send flow with an active chatRunId.
  2. Receive a session.message event for the same session containing a user message echo.
  3. Observe whether the handler reloads chat.history.

Expected

  • The optimistic local user message remains visible and the handler skips the redundant reload.

Actual

  • The focused regression test now passes and confirms the reload is skipped in that case.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran pnpm test ui/src/ui/app-gateway.sessions.node.test.ts and confirmed the new regression test plus existing session-message coverage pass.
  • Edge cases checked: same-session events still reload when no active run is present; other-session events remain ignored.
  • What you did not verify: manual browser reproduction in a running Control UI instance.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: a non-local user session.message event could be delayed if it arrives while chatRunId is active and only exposes role: user.
    • Mitigation: the guard is narrowly limited to the active run window and only the user-echo case that is already rendered optimistically; final assistant events and other transcript reload paths still run.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

This PR fixes a chat transcript flicker in the Control UI where the session.message gateway event handler unconditionally reloaded chat.history — including when echoing the active run's own optimistic user message — briefly replacing the already-rendered message with a stale snapshot. The fix adds a narrow guard that skips the reload only when host.chatRunId is set and the incoming message role is "user", and adds a focused regression test.

Confidence Score: 5/5

Safe to merge — narrow, well-targeted bug fix with a direct regression test and no impact on other reload paths.

No P0 or P1 issues found. The guard is correctly scoped to only the optimistic user-echo case during an active run; assistant-message echoes and events without an active run still trigger the history reload as expected. All findings are P2 or lower.

No files require special attention.

Reviews (1): Last reviewed commit: "Control UI: avoid chat flicker on sessio..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7b46f4b94

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-gateway.ts Outdated
Comment on lines +433 to +434
if (host.chatRunId && role === "user") {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Narrow user-message skip to only local optimistic echo

This guard drops all session.message events with role: "user" whenever chatRunId is set, not just the local optimistic message echo. In handleSessionMessageGatewayEvent, that means a real user message from another client/channel in the same active session is ignored and loadChatHistory is never called for it, so the transcript can stay stale until some later unrelated reload trigger. Please scope this suppression to the specific optimistic message being echoed (instead of role-only filtering) so concurrent user updates are not lost.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97cd4ee1ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-gateway.ts Outdated
Comment on lines +435 to +439
const hasStableMessageId =
message &&
typeof message === "object" &&
(typeof (message as { id?: unknown }).id === "string" ||
typeof (message as { messageId?: unknown }).messageId === "string");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match optimistic echoes using gateway messageId metadata

matchesOptimisticUserEcho currently requires message.id/message.messageId, but gateway transcript events populate IDs as payload.messageId and message.__openclaw.id (see src/gateway/server-session-events.ts) and the webchat user-turn path emits session.message without a top-level message.id (src/gateway/server-methods/chat.ts around emitSessionTranscriptUpdate). In that common send flow, hasStableMessageId is false, so this branch never suppresses the redundant loadChatHistory call and the user-message flicker remains during active runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef289388da

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-gateway.ts
hasStableMessageId &&
lastRole === "user" &&
!lastHasStableMessageId &&
extractText(lastMessage) === extractText(message);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require unique message match before skipping session reload

Tying matchesOptimisticUserEcho to extractText(lastMessage) === extractText(message) can suppress real remote user updates, not just the local optimistic echo. During an active run, if another client sends a user message with the same visible text (or any image-only message where both sides extract to null), this condition still evaluates true and loadChatHistory is skipped, so the transcript can remain stale until some later unrelated reload. The guard should include a stronger identity check (for example explicit optimistic-message correlation) instead of text-only equality.

Useful? React with 👍 / 👎.

@prtags
Copy link
Copy Markdown

prtags Bot commented Apr 23, 2026

Related work from PRtags group evident-terrier-4hzj

Title: Open PR duplicate: Control UI active chat history reload flicker

Number Title
#66271* Control UI: avoid chat flicker on session reload
#67037 fix(ui): skip session.message reloads during active chat

* This PR

@steipete
Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already ships this Control UI fix through a different implementation: same-session session.message reloads are deferred while a chat run is active and replayed after terminal states. The behavior is documented in the changelog for v2026.4.15 and covered by current tests, so this PR is superseded.

What I checked:

  • Shipped release note matches the reported bug: v2026.4.15 changelog explicitly says Control UI keeps optimistic user message cards visible during active sends by deferring same-session history reloads until the active run ends, including aborted and errored runs (#66997). (CHANGELOG.md:836, 9a0b26cafca7)
  • Current gateway defers mid-run session reloads: handleSessionMessageGatewayEvent now skips loadChatHistory(...) whenever host.chatRunId is set and stores a deferred reload key instead; the inline comment describes the same optimistic-user-message flicker race from this PR. (ui/src/ui/app-gateway.ts:496, 9a0b26cafca7)
  • Current gateway replays the deferred reload after terminal events: handleChatGatewayEvent checks pendingSessionMessageReloadSessionKey and replays the deferred loadChatHistory(...) once the matching run reaches a terminal state and chatRunId is cleared. (ui/src/ui/app-gateway.ts:463, 9a0b26cafca7)
  • Current unit test covers skipping reload during active run: handleGatewayEvent session.message test asserts no history reload happens for the active session while chatRunId is set. (ui/src/ui/app-gateway.sessions.node.test.ts:147, 9a0b26cafca7)
  • Current integration-style test covers deferred replay on abort/error: app-gateway.node.test.ts verifies a deferred session.message reload is replayed after aborted and error terminal chat events clear the active run. (ui/src/ui/app-gateway.node.test.ts:724, 9a0b26cafca7)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 9a0b26cafca7; fix evidence: release v2026.4.15.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Control UI chat flickers sent user messages, they disappear briefly then reappear

2 participants