Skip to content

fix(ui): webchat run-status-label stuck on "In progress" after model call ends #86939

@scotthuang

Description

@scotthuang

Bug

After a model call completes in the webchat dashboard, the agent-chat__run-status-label briefly shows "Done", then flips back to "In progress" and stays there permanently until page refresh.

Root Cause

Race condition between the terminal chat event and the sessions.list / sessions.changed response timing:

  1. Agent run ends → chat "final" event arrives → UI sets chatRunStatus = { phase: "done" }, clears chatRunId → shows "Done" ✓
  2. loadSessions is triggered (refresh after chat) → sends sessions.list RPC to gateway
  3. Gateway responds with status: "running" because its session store hasn't been updated yet (async write)
  4. loadSessions replaces sessionsResult → current session row gets status: "running" (overwriting the locally-reconciled "done")
  5. 5-second toast timer fires → chatRunStatus = null
  6. Now: isSessionRunActive({status: "running"}) = truecanAbort = true, hasTerminalRunStatus(null) = falseshowAbortableUi = true"In progress" displayed permanently

The same race applies to sessions.changed events that arrive after the terminal chat event with stale status: "running".

Key insight from debugging

[DEBUG hasAbortableSessionRun] true via session row: {
  key: 'agent:main:dashboard:...',
  status: 'running',    ← stale from server
  hasActiveRun: false   ← correctly cleared
}

isSessionRunActive checks status first (line 9-10 of session-run-state.ts): if status === "running" it returns true regardless of hasActiveRun.

Minimal Fix

In ui/src/ui/controllers/sessions.ts, after loadSessions response and in the sessions.changed event handler, if chatRunId is already null (local run ended), force-correct stale server values:

// After loadSessions replaces sessionsResult (~line 691):
if (!state.chatRunId && hasCurrentChatSession(state)) {
  const currentRow = state.sessionsResult.sessions.find(
    (row) => row.key === state.sessionKey,
  );
  if (currentRow && (currentRow.hasActiveRun === true || currentRow.status === "running")) {
    state.sessionsResult = {
      ...state.sessionsResult,
      sessions: state.sessionsResult.sessions.map((row) =>
        row.key === state.sessionKey
          ? { ...row, hasActiveRun: false, status: row.status === "running" ? "done" : row.status }
          : row,
      ),
    };
  }
}

// In applySessionsChangedEvent, after the field-copy loop (~line 457):
if (
  !state.chatRunId &&
  hasCurrentChatSession(state) &&
  nextRow.key === state.sessionKey
) {
  if (nextRow.hasActiveRun === true) {
    nextRow.hasActiveRun = false;
  }
  if (nextRow.status === "running") {
    nextRow.status = "done";
  }
}

Verified

  • Reproduced on source-built gateway (2026.5.26, macOS, Node v25.8.1, model hy3-preview via tencent-tokenhub)
  • Fix confirmed: after applying the above patch, status stays "Done" and does not flip back
  • Debug log confirms the issue is status: "running" from server, not hasActiveRun

Affected files

  • ui/src/ui/controllers/sessions.ts (both loadSessions and applySessionsChangedEvent paths)

Related context

  • ui/src/ui/session-run-state.ts:9-10isSessionRunActive prioritizes status over hasActiveRun
  • ui/src/ui/views/chat.ts:1069-1070showAbortableUi forces "in-progress" when canAbort && !hasTerminalRunStatus
  • ui/src/ui/chat/run-lifecycle.ts:5 — 5-second toast timer (CHAT_RUN_STATUS_TOAST_DURATION_MS)

Metadata

Metadata

Assignees

Labels

P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

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