Skip to content

fix(ui): clear pending run state on cross-run final to fix stuck typing indicator#57887

Closed
haoyu-haoyu wants to merge 1 commit intoopenclaw:mainfrom
haoyu-haoyu:fix/webchat-typing-stuck-after-subagent
Closed

fix(ui): clear pending run state on cross-run final to fix stuck typing indicator#57887
haoyu-haoyu wants to merge 1 commit intoopenclaw:mainfrom
haoyu-haoyu:fix/webchat-typing-stuck-after-subagent

Conversation

@haoyu-haoyu
Copy link
Copy Markdown

Summary

  • When a cross-run final event carries a real assistant message, clear chatRunId, chatStream, and chatStreamStartedAt so the UI exits the typing/processing indicator.
  • NO_REPLY / empty cross-run finals still preserve parent run state (the parent agent may still be processing).

Root cause

In handleChatEvent, the cross-run final branch (line 283–293) appended the subagent's message but returned without clearing the pending run state. This left the parent agent visually stuck in typing state even after the final response was visible. Refreshing the page cleared it immediately — confirming a frontend state bug, not a backend hang.

Validation

  • pnpm test -- ui/src/ui/controllers/chat.test.ts — updated test to expect state cleared on cross-run final with message content
  • Existing tests for NO_REPLY / empty / delta cross-run events remain unchanged

Notes

  • The fix is scoped to the case where finalMessage is present and non-silent. If a cross-run final is NO_REPLY or has no message, the parent run state is preserved since the parent agent may still be actively streaming.
  • This does not affect same-run final handling (line 303–319), which already clears state correctly.

Closes #57795

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 30, 2026

Greptile Summary

This PR fixes a frontend state bug where the typing/processing indicator remained stuck after a subagent's cross-run final event delivered the actual assistant response. The fix is minimal and well-scoped: when a cross-run final carries a real, non-silent finalMessage, the handler now clears chatRunId, chatStream, and chatStreamStartedAt (mirroring what same-run final handling already does on lines 323–325) and consistently returns "final". NO_REPLY and empty cross-run finals are left unchanged, preserving parent-run state when the parent agent may still be active.

Key observations:

  • The change in return value from null"final" for the real-message branch is intentional and correct: the old null was what originally prevented callers from exiting the typing state.
  • The NO_REPLY / no-message paths are untouched and independently tested (lines 128–149), so there is no regression risk for those cases.
  • The test suite was updated to verify the new contract, and existing tests for the quiet paths remain intact.

Confidence Score: 5/5

  • Safe to merge — targeted one-line state-clearing fix with updated tests and no regressions on existing branches.
  • The change is narrow, correctly mirrors the existing same-run final teardown pattern, and is validated by an updated test plus unchanged tests for the NO_REPLY/empty cases. No P0 or P1 issues found.
  • No files require special attention.

Reviews (1): Last reviewed commit: "fix(ui): clear pending run state on cros..." | 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: 6429e8738b

ℹ️ 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 on lines +292 to +294
state.chatRunId = null;
state.chatStream = null;
state.chatStreamStartedAt = null;
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 Preserve active run when processing other-run finals

Clearing chatRunId/stream state in the cross-run branch treats another run’s final event as if the active run had finished, which can incorrectly unlock the chat input and hide stop/tool progress while the original run is still in flight. This is reachable because the gateway emits assistant final events with unrelated run IDs for non-terminal updates (for example chat.inject uses synthetic inject-* run IDs in src/gateway/server-methods/chat.ts:1942-1953), so this code can prematurely drop the active run state before the matching run’s own terminal event arrives.

Useful? React with 👍 / 👎.

…ng indicator

When a subagent completes and sends a final event with a different
runId, the WebChat UI appends the message but did not clear
chatRunId / chatStream / chatStreamStartedAt. This left the parent
agent visually stuck in a typing / processing state even after the
final response was already visible. Refreshing the page cleared it.

Now clear the pending run state when a cross-run final carries a real
(non-NO_REPLY) assistant message. NO_REPLY / empty finals still
preserve the parent run state since the parent agent may still be
processing.

Closes openclaw#57795

Assisted-by: Claude Code
@haoyu-haoyu haoyu-haoyu force-pushed the fix/webchat-typing-stuck-after-subagent branch from 6429e87 to eb2b75b Compare March 30, 2026 20:16
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: eb2b75b506

ℹ️ 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 on lines +292 to +294
state.chatRunId = null;
state.chatStream = null;
state.chatStreamStartedAt = null;
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 Preserve active run state on foreign final events

Clearing chatRunId/stream in the cross-run final branch makes the UI treat any other run’s terminal event as completion of the active run, which is incorrect when the user’s run is still in flight. The gateway still emits assistant final events with different run IDs (for example chat.inject broadcasts runId: inject-* in src/gateway/server-methods/chat.ts), so this path can prematurely drop busy state, hide ongoing tool/progress updates, and let queued messages flush early through isChatBusy/flushChatQueueForEvent even though the original run has not finished.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 26, 2026

Closing this as duplicate or superseded after Codex automated review.

Close PR #57887 as superseded by the canonical open bug #57795. Current main intentionally preserves active WebChat run state for foreign final events, tests assert that behavior, and the gateway still emits real foreign finals with synthetic inject-* run IDs, so this PR's broad clearing approach risks prematurely unlocking the active run. The underlying stuck-typing issue should remain tracked in #57795 for a safer run-ownership-aware fix.

Best possible solution:

Close this unsafe PR and keep #57795 as the canonical tracker. The best fix is a run-ownership-aware WebChat reconciliation change that clears chatRunId and stream state only when the active client run has actually completed, while preserving state for unrelated announce, inject, side-result, or other foreign final events.

What I checked:

  • Current implementation preserves active state for foreign finals: On current main, handleChatEvent branches when payload.runId !== state.chatRunId; for a non-silent foreign final, it appends the message and returns null without clearing chatRunId, chatStream, or chatStreamStartedAt. Same-run finals clear those fields later in the function. (ui/src/ui/controllers/chat.ts:675, 4ad8b613c9b6)
  • Current tests assert the opposite of this PR: The colocated controller test is named appends final payload from another run without clearing active stream and expects chatRunId, chatStream, and chatStreamStartedAt to remain set after a foreign final with assistant content. (ui/src/ui/controllers/chat.test.ts:112, 4ad8b613c9b6)
  • Gateway terminal handling also guards different active runs: handleTerminalChatEvent returns early for terminal chat events whose run id differs from the active run before resetting tool state or flushing queued chat, matching the current contract that foreign terminal events must not complete the active run. (ui/src/ui/app-gateway.ts:412, 4ad8b613c9b6)
  • Foreign final events are a real gateway behavior: The gateway chat.inject handler broadcasts state: "final" chat events with synthetic runId: inject-*, supporting the review concern that not every foreign final represents completion of the active WebChat run. (src/gateway/server-methods/chat.ts:2428, 4ad8b613c9b6)
  • Premature clearing affects busy/queue behavior: isChatBusy depends on chatRunId, and queue flushing returns while the chat is busy. Clearing chatRunId for an unrelated foreign final can therefore make the UI appear idle while the original run is still active. (ui/src/ui/app-chat.ts:63, 4ad8b613c9b6)
  • Prior review identified the same blocker: The Codex review thread on PR fix(ui): clear pending run state on cross-run final to fix stuck typing indicator #57887 flagged this as a P1: clearing state for any foreign final can drop active-run busy state while the original run is still in flight, with chat.inject cited as a concrete source of unrelated final events. (eb2b75b50683)

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against 4ad8b613c9b6.

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 can leave parent agent stuck in typing state after subagent completion

1 participant