Skip to content

fix(ui): skip session.message reloads during active chat#67037

Closed
hansolo949 wants to merge 6 commits intoopenclaw:mainfrom
hansolo949:fix/control-ui-history-reload-race
Closed

fix(ui): skip session.message reloads during active chat#67037
hansolo949 wants to merge 6 commits intoopenclaw:mainfrom
hansolo949:fix/control-ui-history-reload-race

Conversation

@hansolo949
Copy link
Copy Markdown

Summary

  • Problem: Control UI can eagerly reload chat.history on session.message while the current chat is still sending, loading, or streaming.
  • Why it matters: this can race against optimistic/local state and produce flicker, collapse, duplicate bubbles, or temporarily incorrect ordering.
  • What changed: added an active-chat-flow gate in ui/src/ui/app-gateway.ts so same-session session.message events do not reload history during an active chat, and added a regression test for that behavior.
  • What did NOT change (scope boundary): this PR does not redesign full history reconciliation or change unrelated reload paths. It only suppresses one eager reload path during active chat flow.

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: session.message events could trigger loadChatHistory(...) for the active session even while the same chat was still in an active local send/load/stream state.
  • Missing detection / guardrail: there was no active-chat-flow check before reloading history on session.message.
  • Contributing context (if known): eager history replacement races with optimistic user messages and live assistant stream state.

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:
    • when a session.message event arrives for the active session during an active chat flow, the UI does not reload chat history
  • Why this is the smallest reliable guardrail:
    • the bug is specifically in the session.message event handler and can be locked down directly without a broader end-to-end harness
  • Existing test that already covers this (if any):
    • existing coverage only checked that active-session session.message normally reloads history
  • If no new test is added, why not:
    • N/A

User-visible / Behavior Changes

  • Control UI is less likely to flicker, collapse, duplicate, or reorder messages during an active send/stream cycle when session.message arrives for the current session.

Diagram (if applicable)

Before:
[user sends message] -> [chat active]
                     -> [session.message arrives]
                     -> [history reload]
                     -> [optimistic/live state replaced] -> [flicker/duplicate/reorder]

After:
[user sends message] -> [chat active]
                     -> [session.message arrives]
                     -> [reload skipped during active flow]
                     -> [local stream/final state stays stable]

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 OpenClaw install, web Control UI
  • Model/provider: N/A
  • Integration/channel (if any): webchat / Control UI
  • Relevant config (redacted): default local Control UI flow

Steps

  1. Open Control UI and send a message in webchat.
  2. While the chat is still sending/loading/streaming, allow session.message to fire for the same session.
  3. Observe whether chat history reloads and disturbs the visible optimistic/streaming state.

Expected

  • active local chat state remains stable during the send/stream cycle
  • no eager history reload for same-session session.message during active flow

Actual

  • before this change, session.message could trigger history reload during active chat flow and contribute to flicker/collapse/duplicate behavior

Evidence

Attach at least one:

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

Human Verification (required)

  • Verified scenarios:
    • local Control UI behavior improved after suppressing active-flow history reloads
    • same-session active-chat reload path was identified as a plausible causal trigger
  • Edge cases checked:
    • active state via chatRunId
    • active state via sending/loading/stream presence
  • What you did not verify:
    • full upstream CI/test suite in the source repo
    • all other history reload triggers such as unrelated sessions.changed paths

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:
    • skipping reloads during active flow could delay some legitimate same-session hydration until the chat is no longer active
    • Mitigation:
      • the guard is narrow and only applies during active local chat flow
      • normal history loads still occur outside that active window

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: 9ecf377af0

ℹ️ 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

function isActiveChatFlow(host: GatewayHost & Partial<ChatState>): boolean {
return (
host.chatLoading === true ||
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 Remove chatLoading from the session.message suppression gate

Including host.chatLoading in isActiveChatFlow suppresses session.message reloads during any in-flight history fetch, not just during active send/stream. Because loadChatHistory sets chatLoading = true immediately and handleSessionMessageGatewayEvent just returns (without scheduling a retry), a message that arrives after the current chat.history snapshot can be dropped until some unrelated future reload occurs, leaving the active session temporarily stale.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

Adds an isActiveChatFlow guard to handleSessionMessageGatewayEvent in ui/src/ui/app-gateway.ts so that same-session session.message events do not trigger loadChatHistory while the local chat is still sending, streaming, loading, or running. A regression test is included for the chatRunId case. Implementation is correct; the check handles all four active states (chatLoading, chatSending, chatRunId, chatStream) including the empty-string stream initialisation.

Confidence Score: 5/5

Safe to merge — the guard is narrow and correct, and terminal-event reload paths remain intact.

No P0/P1 findings. The sole remaining comment is P2 test coverage: three of the four active-flow conditions go untested. Logic, type safety, and the main regression scenario are all sound.

ui/src/ui/app-gateway.sessions.node.test.ts — missing test cases for chatLoading, chatSending, and chatStream branches of isActiveChatFlow.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/app-gateway.sessions.node.test.ts
Line: 145-159

Comment:
**Test only covers one of four active-flow conditions**

The new test validates the `chatRunId` branch of `isActiveChatFlow`, but the other three conditions — `chatLoading`, `chatSending`, and `chatStream` — each have distinct lifecycle meanings and are not independently exercised. A chat that has started streaming (`chatStream = ""`) but has no `chatRunId` yet, or a reload already in flight (`chatLoading = true`), would also be suppressed. Extending coverage to the other conditions would lock in the full guard boundary.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(ui): skip session.message reloads du..." | Re-trigger Greptile

Comment on lines +145 to +159
it("does not reload chat history during active chat flow", () => {
loadChatHistoryMock.mockReset();
const host = createHost();
host.sessionKey = "agent:qa:main";
host.chatRunId = "run-123";

handleGatewayEvent(host, {
type: "event",
event: "session.message",
payload: { sessionKey: "agent:qa:main" },
seq: 1,
});

expect(loadChatHistoryMock).not.toHaveBeenCalled();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Test only covers one of four active-flow conditions

The new test validates the chatRunId branch of isActiveChatFlow, but the other three conditions — chatLoading, chatSending, and chatStream — each have distinct lifecycle meanings and are not independently exercised. A chat that has started streaming (chatStream = "") but has no chatRunId yet, or a reload already in flight (chatLoading = true), would also be suppressed. Extending coverage to the other conditions would lock in the full guard boundary.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/app-gateway.sessions.node.test.ts
Line: 145-159

Comment:
**Test only covers one of four active-flow conditions**

The new test validates the `chatRunId` branch of `isActiveChatFlow`, but the other three conditions — `chatLoading`, `chatSending`, and `chatStream` — each have distinct lifecycle meanings and are not independently exercised. A chat that has started streaming (`chatStream = ""`) but has no `chatRunId` yet, or a reload already in flight (`chatLoading = true`), would also be suppressed. Extending coverage to the other conditions would lock in the full guard boundary.

How can I resolve this? If you propose a fix, please make it concise.

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: 23e29f3e3d

ℹ️ 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
Comment on lines +439 to +440
if (isActiveChatFlow(host as GatewayHost & Partial<ChatState>)) {
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.

P1 Badge Queue a deferred reload when session.message is suppressed

Returning early for active chat flow drops same-session session.message events without any retry path. Because tracked-run chat finals now skip loadChatHistory when shouldReloadHistoryForFinalEvent(..., { trackedRunId }) is false, a message that lands during chatSending/chatRunId/chatLoading/streaming can be missed indefinitely until an unrelated reload occurs. This makes the active transcript stale in real scenarios like concurrent writers (another client/channel) or transcript updates that race with the in-flight run.

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: 373c94d193

ℹ️ 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 +434 to +435
suppressionHost.suppressNextSessionMessageReloadForSessionKey =
payload?.sessionKey?.trim() || host.sessionKey;
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 Expire one-shot suppression when matching event is not received

Setting suppressNextSessionMessageReloadForSessionKey here assumes the corresponding session.message will always arrive next, but that flag is only cleared when a matching event is handled. Because transcript events are sent with dropIfSlow and connections can reconnect between events, the matching session.message can be missed; in that case this stale flag suppresses the next unrelated session.message for the same session, leaving chat history stale until a later reload trigger happens.

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: b284ff913d

ℹ️ 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
clearDeferredSessionMessageReload(host, payload?.sessionKey);
void loadChatHistory(host as unknown as ChatState);
} else {
suppressNextSessionMessageReload(host, payload?.sessionKey?.trim() || host.sessionKey);
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 Limit one-shot suppression to a confirmed matching update

handleChatGatewayEvent always arms suppressNextSessionMessageReload for tracked finals, but this suppresses the next same-session session.message even when no matching follow-up exists. Fresh evidence: gateway finals can be emitted without a message payload (state: "final" with optional message), and transcript events are sent with dropIfSlow, so the expected paired session.message is not guaranteed; in that case this line drops an unrelated real update and the chat can remain stale until another reload path runs.

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

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 26, 2026

Closing this as implemented after Codex automated review.

Current main already implements the useful part of PR #67037: same-session session.message reloads are skipped while a chat run is active, the skipped update is deferred and replayed when the active run reaches a terminal event, and history reloads preserve optimistic local messages so stale/empty snapshots no longer flicker or erase visible chat state. The PR review discussion also raised dropped-reload risks in the PR implementation; current main addresses that risk with a deferred reload rather than a plain suppression.

Best possible solution:

Close PR #67037 as implemented on current main. Keep the shipped current-main approach: defer same-session reloads only while an actual chat run is active, replay the deferred reload after terminal events, and preserve optimistic transcript entries during stale or empty history refreshes. Use #66875 or a new narrow issue for any remaining flicker path that still reproduces on current main.

What I checked:

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

Codex Review notes: model gpt-5.5, reasoning high; reviewed against d54d2d6b9b8a; fix evidence: release v2026.4.24, commit d54d2d6b9b8a.

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.

Webchat race: chat final / session.message / sessions.changed triggers eager chat.history reload, causing flicker, collapse, or duplicate bubbles

1 participant