Files
platform/todo/known-bugs/refresh-mid-tool-call-renders-the-turn-folded.md
pastilhasandClaude Opus 5 6b8cf848ff a turn this tab did not start is still a turn in progress
"Turn finished · no closing message" kept appearing over an agent that was
plainly still working, with output resuming a moment later.

isGenerating was set in exactly three places: sendPrompt, and the two reconnect
handshakes. So it answered "am I waiting for a reply I asked for", never "is
this conversation working". Every other way a turn begins left it false — a
commander waking on its own schedule, a queued prompt the server delivered,
another tab driving the same session, or a turn that started before this tab
was looking. The events still render, but buildRows reads isGenerating to
decide whether the last turn is finished, so a live turn was `complete` and the
moment it sat on a completed tool call with nothing said after it, the banner
fired. Same gap left the composer enabled mid-turn, and a message typed then
was not marked injected — which splits the live turn and prints the banner a
second way, the exact failure injected exists to prevent.

session:claude now sets it: the harness's own turn-start signal, and the only
one that arrives before any work does. tool:start sets it too, because
session:claude is not durable and a socket connecting mid-turn never receives
one, while a tool cannot start outside a turn.

Two candidates were measured rather than argued. A genuinely silent turn ending
earns the banner honestly, but 667 of 673 recorded turns end with
assistant:text immediately before result, so that is ~1% and cannot be it. And
a stale sync:live is real but rare: correlating every attach in the officer log
against the agent's own later output, 15 of 549 "not generating" attaches were
contradicted and one provably so. That residue is left alone — the tool:start
fallback now corrects it on the next event — and noted on the refresh card,
which is the same seam.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: f7a62308-0977-4cf4-aa29-c020853fd420
2026-08-25 15:27:49 +00:00

4.1 KiB

id, title, theme, status, created, date_source, order
id title theme status created date_source order
known-bugs/refresh-mid-tool-call-renders-the-turn-folded Refresh mid-turn folds the live tool call — it reads as finished while still running known-bugs open 2026-08-25 stated 5

Refresh mid-turn folds the live tool call. Reported 2026-08-25: refreshing while an agent is mid tool-run collapses that turn's tool calls instead of leaving them expanded, so the chat reads as finished when the agent is still working. Requested fix: the last, still-running turn must always render EXPANDED across a refresh or a back-navigation, never folded.

This is not unbuilt — there is already code for exactly this case, and it is being defeated somewhere between the transcript load and the live-state confirmation. turn-rows.ts's buildRows computes generatingNow = isLast && isGenerating and refuses to fold the last turn when it is true — a comment there names this precise failure mode: "for those few frames every unfinished turn looks finished." So the intended behaviour is real; the bug is that the frames it warns about are not always brief.

Where the race most likely lives, traced but not proven — hooks/useChat.ts:

  • historicalCount starts at preloadedMessages?.length ?? 0 (line ~110) — the whole HTTP-loaded transcript, tool call included, counted as history from the first render.
  • isGenerating starts false and is only set true when the socket confirms it — via sync:live (rebinding to a turn that kept running) or sync:messages (a full resume).
  • sync:live's own comment says messages are "deliberately absent — the transcript already loaded over HTTP" — it sets isGenerating but does not touch historicalCount. sync:messages, by contrast, sets both together in one call (setHistoricalCount(transcript.length) immediately before setIsGenerating(msg.isGenerating)), which is the version that should self-correct.

So the two socket messages that confirm "still generating" do not treat historicalCount the same way, and a refresh's actual sequence — HTTP transcript first, socket confirmation after — is exactly the gap the generatingNow guard was written to close but may not fully reach depending on which message arrives. Worth checking first: whether sync:live reliably fires before the user notices, and whether server-side session.isGenerating (true from turn-start to turn-end in claude-manager.ts, tool calls included) is actually what reaches the client in that message — or whether it's a timing gap rather than a wrong value.

Possibly related, same neighbourhood (refresh + a live turn + folding), not confirmed as the same cause: known-bugs/refreshing-a-finished-chat-turn-loses-output — a different symptom (a turn that DID finish loses its output entirely and misreports 0 turns · 0 tok) but the same refresh-time seam between what the transcript says and what the live socket confirms.

2026-08-25 — partly overtaken, and the measurement is worth having. A separate cause was found and fixed for the same family of symptom: isGenerating was never set by anything except sendPrompt and the two handshakes, so a turn this tab did not start rendered as finished from the first event onwards. See known-bugs/a-turn-this-tab-did-not-start-reads-as-finished. session:claude and tool:start now set it, which means the last turn stops folding as soon as any work arrives — so the window this card is about is now bounded by the next event rather than open-ended.

What is left here is the question this card actually asks, and it is now measured rather than suspected: correlating every attach in the officer log against the agent's own subsequent durable output, 15 of 549 attaches that told a client "not generating" were contradicted, one of them provably — the first event after it was a tool:result, so a tool was already in flight when sidecar.isClaudeGenerating answered. So sync:live does sometimes carry a wrong value, it is rare, and it is a wrong ANSWER rather than the timing gap this card assumed. historicalCount was not implicated.