Skip to content

fix #87699: [Bug]: [BUG] UI shows agent "running" after conversation ends β€” requires manual page refresh every time#89727

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
zhangguiping-xydt:feat/issue-87699
Jun 3, 2026
Merged

fix #87699: [Bug]: [BUG] UI shows agent "running" after conversation ends β€” requires manual page refresh every time#89727
vincentkoc merged 2 commits into
openclaw:mainfrom
zhangguiping-xydt:feat/issue-87699

Conversation

@zhangguiping-xydt

@zhangguiping-xydt zhangguiping-xydt commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Root Cause

  • Root cause: When chat.send returned a terminal ok ACK, the Control UI only cleared a few local stream fields. It did not publish a terminal run status or arm the existing local terminal reconciliation window. A later sessions.list response could therefore reintroduce a stale active row and make the composer show Stop / In progress after the conversation had already finished.

  • Why this is root-cause fix: The existing lifecycle helper is already the single place that clears local run state, writes terminal session state, publishes terminal UI status, and arms stale-row suppression. Routing ACK ok through that helper makes the synchronous completion path follow the same invariant as other terminal run paths instead of only hiding part of the local stream state.

Verification

  • node scripts/run-vitest.mjs ui/src/ui/app-chat.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/chat/run-lifecycle.test.ts β€” 3 files passed, 184 tests passed.
  • node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo β€” passed on follow-up commit a7db91173c, covering the CI failure reported by check-test-types.
  • Earlier issue-mode proof also ran node scripts/run-vitest.mjs ui/src/ui/app-gateway.sessions.node.test.ts β€” 1 file passed, 29 tests passed.
  • Real Control UI proof through a local dev gateway serving this branch: a chat prompt completed in the browser, then the selected chat stayed idle immediately after completion and again 8 seconds later.

Regression Test Plan

  • Target test file: ui/src/ui/app-chat.test.ts, ui/src/ui/controllers/chat.test.ts, ui/src/ui/chat/run-lifecycle.test.ts, and ui/src/ui/app-gateway.sessions.node.test.ts.

  • Patch quality notes: The production change is limited to the two Control UI chat.send ACK ok branches. It reuses the existing run lifecycle helper instead of adding a second cleanup path. The new regression test simulates the race that matters for [Bug]: [BUG] UI shows agent "running" after conversation ends β€” requires manual page refresh every timeΒ #87699: terminal ACK first, then stale active sessions.list data. The follow-up commit is test-only and fixes the TypeScript cast shape used by that regression test.

  • Fix classification: Root-cause UI session-state bug fix plus test-only CI type fix.

  • Maintainer-ready confidence: High for the touched desktop Control UI chat path because the fix is small, covered by targeted regression tests, verified through a live local Control UI run, and the current head has the failing test-types shard reproduced locally.

Real behavior proof

  • Behavior or issue addressed: The Control UI should not keep showing Stop / In progress after a chat turn has completed.

  • Why it matters / User impact: Before this fix, users could see an agent as still running after the visible conversation ended and had to refresh the page before sending reliably again. The fix restores the expected idle composer state without a manual refresh.

  • Real environment tested: Local dev gateway serving the patched Control UI from this branch, opened in headless Chromium through Playwright.

  • Exact steps or command run after this patch: Started a local dev gateway on an isolated port, opened /chat in Chromium, sent Reply exactly: OC_LIVE_OK_87699, waited for an assistant response containing OC_LIVE_OK_87699, then sampled the app state immediately and 8 seconds later.

  • Evidence after fix: After the assistant response appeared, the browser state had chatRunId=null, chatStream=null, session hasActiveRun=false, Stop button count 0, and no In progress text.

Copied live output:

passed: True
url: http://127.0.0.1:19103/chat
afterAssistantVisible {'connected': True, 'chatSending': False, 'chatRunId': None, 'chatStream': None, 'chatRunStatus': {'phase': 'done', 'runId': 'bfb1fe18-4a10-4529-8b3d-931089c7e1da', 'sessionKey': 'agent:main:main'}, 'sessionKey': 'agent:main:main', 'sessions': [{'key': 'agent:main:main', 'status': 'done', 'hasActiveRun': False}], 'stopButtonCount': 0, 'hasInProgressText': False, 'hasDoneText': True}
afterEightSeconds {'connected': True, 'chatSending': False, 'chatRunId': None, 'chatStream': None, 'chatRunStatus': None, 'sessionKey': 'agent:main:main', 'sessions': [{'key': 'agent:main:main', 'status': 'running', 'hasActiveRun': False}], 'stopButtonCount': 0, 'hasInProgressText': False, 'hasDoneText': False}
  • Observed result after fix: The composer returned to the normal Send state without requiring a manual page refresh; 8 seconds later Stop was still absent and In progress was still absent.

  • What was not tested: I did not run the full UI suite or remote CI locally. The follow-up commit only changes a test cast, so I re-ran the failed typecheck shard and the related UI chat tests instead of repeating the browser live proof.

Review findings addressed

  • RF-001 (P1, ClawSweeper session-state risk): Fixed/covered on current head. Terminal ACK ok handling passes the gateway ACK runId and the current sessionKey into the existing lifecycle reconciliation helper, so the stale active-row suppression is scoped through the same session lifecycle path as other terminal outcomes. The regression test covers terminal ACK completion followed by stale active sessions.list data, and the live Control UI proof shows chatRunId=null, hasActiveRun=false, no Stop button, and no In progress text after completion.

  • RF-002 (P2, no repair lane needed): Addressed on current head. The repair lane only added a test-only unknown cast to clear the PR-introduced check-test-types failure; the production fix remains the same focused Control UI session-state reconciliation change.

  • Linked issue live-proof request: Satisfied by the local dev gateway + headless Chromium proof copied above. The follow-up commit is test-only, so I re-ran the failed typecheck shard and related UI chat regression tests instead of repeating the browser proof.

Merge risk

  • Risk labels considered: merge-risk:session-state is relevant because this changes Control UI run-state reconciliation. Auth-provider, automation, availability, compatibility, message-delivery, security-boundary, protocol, public API, public config, and schema risks are not applicable to this PR because the committed diff only touches Control UI run-state handling and UI tests.

  • Risk explanation: The behavior change is intentionally narrow: only terminal ACK ok handling changes. Non-terminal started / in_flight ACKs still adopt the run id as before, and gateway protocol/config/auth/channel behavior is unchanged.

  • Why acceptable: The existing lifecycle helper already owns terminal run cleanup and stale active-row suppression. Using it for ACK ok removes a one-off cleanup branch and makes the completion path more consistent with the rest of the Control UI.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 3, 2026
@clawsweeper

clawsweeper Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 3, 2026, 4:22 AM ET / 08:22 UTC.

Summary
The PR routes terminal Control UI chat.send ACKs through chat run lifecycle reconciliation in direct and queued send paths, and adds regression coverage for stale sessions.list active rows.

PR surface: Source +11, Tests +52. Total +63 across 4 files.

Reproducibility: yes. Source inspection shows current main clears only local ACK fields and does not arm lastLocalTerminalReconcile, while the PR's regression test models terminal ACK completion followed by a stale active sessions.list row.

Review metrics: none identified.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] This intentionally changes Control UI session-state reconciliation: if the ACK completion scope were wrong, Stop / In progress could clear or stay suppressed incorrectly for the selected session, although the patch uses the existing helper and targeted proof covers the reported race.

Maintainer options:

  1. Accept The Covered Session-State Risk (recommended)
    Maintainers can accept this narrow risk if the current head checks pass, because the branch reuses the existing lifecycle helper and includes targeted regression plus live Control UI proof for the stale-row race.
  2. Ask For Broader UI Gate Before Merge
    If maintainers want extra confidence, require the relevant broader Control UI gate on the exact head SHA before landing.

Next step before merge

  • [P2] No repair lane is needed; the remaining action is normal maintainer and CI review of an already-focused PR branch.

Security
Cleared: The diff only touches Control UI TypeScript state handling and tests; I found no concrete security or supply-chain concern.

Review details

Best possible solution:

Land the focused Control UI lifecycle-helper fix after normal maintainer and CI review, without changing gateway protocol or adding a second run-state cleanup path.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main clears only local ACK fields and does not arm lastLocalTerminalReconcile, while the PR's regression test models terminal ACK completion followed by a stale active sessions.list row.

Is this the best way to solve the issue?

Yes. Routing ACK completion through the existing run lifecycle helper is the narrowest maintainable fix; changing gateway/session protocol or adding another cleanup branch would duplicate the owner path.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 34c3827290a7.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body supplies after-fix live Control UI output from a local gateway and Chromium showing the composer idle immediately after completion and again 8 seconds later.

Label justifications:

  • P1: The linked bug blocks repeated Control UI chat turns by leaving the composer in a running state until refresh, which is an urgent broken user workflow.
  • merge-risk: 🚨 session-state: The diff changes how terminal ACK completion updates local run state and suppresses stale selected-session active rows.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: πŸ‘€ ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body supplies after-fix live Control UI output from a local gateway and Chromium showing the composer idle immediately after completion and again 8 seconds later.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body supplies after-fix live Control UI output from a local gateway and Chromium showing the composer idle immediately after completion and again 8 seconds later.
Evidence reviewed

PR surface:

Source +11, Tests +52. Total +63 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 15 4 +11
Tests 2 52 0 +52
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 67 4 +63

What I checked:

  • Repository policy read: Read the full root AGENTS.md and scoped ui/AGENTS.md; the review applied the OpenClaw rule to inspect the changed module, callers/callees, sibling lifecycle paths, tests, proof, and history before verdict. (AGENTS.md:1, 34c3827290a7)
  • Current direct ACK behavior: On current main, sendChatMessage handles ack.status === "ok" by clearing only chatRunId, chatStream, and chatStreamStartedAt; it does not publish a terminal lifecycle outcome or arm local terminal reconciliation. (ui/src/ui/controllers/chat.ts:951, 34c3827290a7)
  • Current queued ACK behavior: On current main, the queued ACK ok path calls reconcileChatRunLifecycle without outcome or armLocalTerminalReconcile, so later sessions.list reconciliation has no recent local terminal completion to suppress a stale active row. (ui/src/ui/app-chat.ts:927, 34c3827290a7)
  • Lifecycle helper contract: reconcileChatRunLifecycle can arm lastLocalTerminalReconcile, and reconcileChatRunFromCurrentSessionRow uses that recent local terminal state to turn a racing active sessions.list row back into terminal state during the 10-second window. (ui/src/ui/chat/run-lifecycle.ts:177, 34c3827290a7)
  • Sessions caller path: loadSessions replaces sessionsResult from sessions.list and then calls reconcileChatRunFromCurrentSessionRow, which is the callee that can clear or suppress selected-chat run state after refreshed session rows arrive. (ui/src/ui/controllers/sessions.ts:1147, 34c3827290a7)
  • PR diff proof: The PR changes both ACK ok branches to pass outcome: "done" and armLocalTerminalReconcile: true through the existing helper, and adds a regression test that sends an ACK-completed turn followed by a stale active sessions.list row. (ui/src/ui/app-chat.ts:928, fb7a332d2749)

Likely related people:

  • vincentkoc: Current-main blame and file history for app-chat.ts, controllers/chat.ts, controllers/sessions.ts, and chat/run-lifecycle.ts point to Vincent Koc's recent Control UI session lifecycle work in the local checkout. (role: recent area contributor; confidence: medium; commits: ac8338bb026d, 34c3827290a7; files: ui/src/ui/app-chat.ts, ui/src/ui/controllers/chat.ts, ui/src/ui/controllers/sessions.ts)
What the crustacean ranks mean
  • πŸ¦€ challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • πŸ¦ͺ silver shellfish: thin signal; proof, validation, or implementation needs work.
  • πŸ§‚ unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: πŸ‘€ ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jun 3, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 3, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 3, 2026
@vincentkoc vincentkoc self-assigned this Jun 3, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 3, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 3, 2026
@vincentkoc

Copy link
Copy Markdown
Member

Maintainer verification after rebasing onto current origin/main (932d6ea8e5cef3de5d672e1a68b682492c910439):

  • Reviewed the ACK-complete WebChat path from requestChatSend through sendQueuedChatMessage, sendChatMessage, reconcileChatRunLifecycle, loadSessions, and stale selected-session row reconciliation.
  • Confirmed the main Control UI composer path keeps publishRunStatus: false, so this does not add a new Done banner while it arms the existing stale-active-row suppression.
  • Local proof:
    • node scripts/run-vitest.mjs run ui/src/ui/app-chat.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/chat/run-lifecycle.test.ts β€” 3 files, 184 tests passed.
    • node scripts/run-vitest.mjs run ui/src/ui/app-chat.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/chat/run-lifecycle.test.ts ui/src/ui/controllers/sessions.test.ts -- -t "ACK-completed|adopts the run id|stale-active suppression|arms suppression|current session row|sessions.list|stale active row|local completion" β€” 2 shards passed, 19 focused tests total.
    • node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/chat-flow.e2e.test.ts β€” 1 file, 11 tests passed.
    • git diff --check origin/main...HEAD β€” passed.
  • Autoreview:
    • .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main β€” clean; no accepted/actionable findings; overall patch is correct (0.84).
  • Testbox changed gate:
    • node scripts/crabbox-wrapper.mjs run --provider blacksmith-testbox -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed β€” provider blacksmith-testbox, id tbx_01kt68xvz17fcnmd3wj6f7pk6f, run https://github.com/openclaw/openclaw/actions/runs/26872494563, exit 0; Testbox stopped.
  • PR CI:
    • Head fb7a332d2749cf0506c429be5cf18c8e0ee6faae is MERGEABLE / CLEAN; 145 checks green or neutral.
    • Reran failed jobs only after a Blacksmith/GitHub runner setup flake left several shards failed before their Run Node test shard step; rerun passed.

@vincentkoc vincentkoc merged commit 0b98aea into openclaw:main Jun 3, 2026
257 of 266 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: πŸ‘€ ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: [BUG] UI shows agent "running" after conversation ends β€” requires manual page refresh every time

2 participants