Skip to content

fix(chat): "Working…" shows or goes at the wrong time around a turn #2550

Closed

Description

@jpcarranza94

What happened

A bug hunt on Ovis Chat (headless Chromium, a frame recorder, the demo and a live Claude Code pane) found four places where the chat's "Working…" state shows or goes at the wrong moment.

  1. A finished turn opens again as "Working…". A turn ended and reads › Worked for 2s · 1 other tool, with its answer under it. A background subagent then finishes, and Claude starts a new turn by itself. For 200–400 ms the old block reads ∨ ● Working… · 1 other tool and opens its tool row. Then the new turn's first row lands, and the old block folds a second time. Live timeline (Claude Code 2.1.316, haiku):

    54492ms  the turn ends: its block folds to "Worked for 2s · 1 other tool"   (normal)
    58342ms  composer status BG -> RUN                (Claude starts a turn by itself)
    58557ms  "Background task ended" row shows, with chat-activity "Working…"
    58742ms  the old block folds again: same text change as at 54492ms
    

    In the demo, a status push to working with no new row reopened the turn every time. The row grew from 145 px to 274 px.

  2. "Working…" shows before a working pane's rows. A switch to a pane whose agent works draws a "Working…" work-block head next to "Loading conversation…". When the first read lands, the head goes and "Working…" comes back as the title of the running turn's own block, in the last row. In the demo it lasted one frame (18 ms) in 1 of 3 runs. A held first read shows the head for the whole read. On a real server, the first read of a chat takes about 150–500 ms.

  3. "Working…" goes about 1 s before Claude's answer. At the end of a long answer, "Working…" and the Stop button go, and the chat moves 34 px. For a moment only the user's message shows, as if the turn ended with no answer. Then the answer is drawn. Live: a 984 ms gap in 1 of 12 turns (the conversation reads at 142240 ms returned unchanged, and the answer came with the read at 143256 ms). A second check saw a 133 ms gap in 1 of 3 80-line answers.

  4. /clear in an empty Claude chat flashes "Conversation unavailable". The greeting and the centred composer go, the composer drops to the bottom, and the chat shows ▸ Conversation unavailable — show terminal output and "Working…". Then the greeting comes back. Live: 241 ms in the first run, and 549 ms and 295 ms in 2 of 5 later /clear runs typed in the terminal. The other 3 left herdr's status at rest.

Expected: a finished turn stays finished, "Working…" shows only for a turn in progress and only once the chat knows its rows, and a short status change after /clear leaves the greeting in place.

Cause

  1. isLiveWorkTurn (src/lib/workBlocks.ts:179-182) calls the last assistant turn live whenever the pane's status is working. The only exception is sentOver (src/components/ChatView.tsx:1424), which a send from this browser sets. A turn that the agent starts by itself, or that starts from the terminal or another device, sets nothing. When herdr's working status comes before the new turn's first row, the finished turn is the last one, so ChatView.tsx:2070 and :2195 title it "Working…" and open it. The transcript already says the turn is over: its last part is a text with phase final_answer (Claude gives it only to an end_turn entry).
  2. The activity line at src/components/ChatView.tsx:2257 has no loaded gate. The empty state at :2265 has one. Before the first read, lastSplit (:2070) is null, so workBlockLive (:2073) is false and the footer draws the PendingWorkHead.
  3. For Claude Code, the activity is herdr's status (ChatView.tsx:1230). The line at :2257 and the composer's Stop button (src/components/Composer.tsx:1470) follow it. herdr can go done before a conversation read holds the answer. The edge read (ChatView.tsx:1432) starts at once, but nothing holds the line until a read made after the edge is drawn.
  4. /clear makes herdr report working for a moment. showsGreeting (src/lib/greeting.ts:113-117) hides the greeting while the agent works, so the composer docks. On the server, onNotStarted (server/conversation.ts:1637-1647) sends a session with no transcript yet to the terminal fallback while the agent works or is blocked. Each part is on purpose (a first turn at work, a message just sent), but a /clear starts both of them.

Proposed fix

One PR for 1 and 2:

  • isLiveWorkTurn: while the status is working, a turn whose last part is a final_answer text is not live. The fix is in the shared function, so every caller gets it. The pending "Working…" head under the rows stands for the new turn until its first row lands, as after a send.
  • That head waits a short time (800 ms) over a turn that ended on its answer. At a normal turn end, herdr's status trails the answer by 100–400 ms. A head drawn at once flashes under every answer for that time.
  • The activity line waits for the first read (loaded), as the empty state does. Until then "Loading conversation…" is the only line.

3 and 4 stay open:

  • 3 needs a hold: on a working → idle or done edge with no answer drawn, keep "Working…" and the Stop button until a read begun after the edge is drawn, with a cap of about 2 s. The Stop button lives in the composer, so the hold crosses components.
  • 4 needs a change to the server's first-turn rule (a history that /clear replaced is not a first turn), or a short delay before a status-only working hides the greeting.

How often

1: when herdr's working status comes before the new turn's first row. 1 of 2 live runs with a background subagent. 2: on a switch to a working pane whose chat has no cached read. 3: rare, 1 of 12 turns. 4: 2 of 5 /clear runs typed in the terminal of an empty chat.

Ovis version

main (feb3ae0 for my test runs, checked again on 53542d6).

How it runs

herdr plugin

Server PC

macOS 26.5.2, Apple M4 (Mac mini), herdr 0.9.0, Claude Code 2.1.316

Device and browser

Headless Chromium at 1280×800. The causes are not specific to a device.

Activity

  1. added a commit that references this issue on Nov 4, 2026
    3f5844e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions