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
This commit is contained in:
@@ -21,42 +21,42 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
||||
|
||||
## Themes
|
||||
|
||||
**97 open, 30 closed**, across 32 themes.
|
||||
**97 open, 31 closed**, across 32 themes.
|
||||
|
||||
| Theme | Open | Closed |
|
||||
| ------------------------------------------------------------------------------------------ | ---- | ------ |
|
||||
| [Affordances](todo/affordances/) | 2 | 0 |
|
||||
| [Agents coordinating agents](todo/agent-coordination/) | 3 | 0 |
|
||||
| [Speaking into a session from the backend](todo/agent-delivery/) | 1 | 1 |
|
||||
| [Backburner](todo/backburner/) | 3 | 0 |
|
||||
| [Background jobs](todo/background-jobs/) | 2 | 1 |
|
||||
| [Chat interface](todo/chat/) | 1 | 0 |
|
||||
| [Code editor](todo/code-editor/) | 1 | 0 |
|
||||
| [Components — the shared UI layer](todo/components/) | 1 | 0 |
|
||||
| [File watchers](todo/file-watchers/) | 1 | 0 |
|
||||
| [Fleet — one command across every server](todo/fleet/) | 5 | 2 |
|
||||
| [Headscale](todo/headscale/) | 0 | 5 |
|
||||
| [Infra (alpha)](todo/infra/) | 7 | 0 |
|
||||
| [Known bugs](todo/known-bugs/) | 5 | 3 |
|
||||
| [Memory — one store instead of per-harness files](todo/memory/) | 1 | 0 |
|
||||
| [Mobile apps](todo/mobile-apps/) | 4 | 0 |
|
||||
| [Multi-user](todo/multi-user/) | 17 | 5 |
|
||||
| [Notifications](todo/notifications/) | 1 | 0 |
|
||||
| [Plugin ecosystem — before it opens to third parties (2026-08-18)](todo/plugin-ecosystem/) | 3 | 0 |
|
||||
| [Plugin infrastructure](todo/plugin-infrastructure/) | 2 | 0 |
|
||||
| [Plugin repos — what writing their READMEs turned up](todo/plugin-repos/) | 10 | 9 |
|
||||
| [Plugin system — gaps the email extraction found (2026-08-18)](todo/plugin-system/) | 3 | 0 |
|
||||
| [Prompt library](todo/prompt-library/) | 1 | 0 |
|
||||
| [SendOff](todo/sendoff/) | 1 | 0 |
|
||||
| [Settings — portable, and readable by an agent](todo/settings/) | 2 | 0 |
|
||||
| [Shell — spotlight and the dock](todo/shell/) | 4 | 1 |
|
||||
| [Sub-agents — agents spawning agents](todo/sub-agents/) | 0 | 2 |
|
||||
| [Sudo terminal modal](todo/sudo-terminal-modal/) | 1 | 0 |
|
||||
| [Tooling — the list about the list](todo/tooling/) | 0 | 1 |
|
||||
| [Transmission](todo/transmission/) | 6 | 0 |
|
||||
| [URL state — routing, query strings and selection](todo/url-state/) | 4 | 0 |
|
||||
| [Waiting on the owner to run it](todo/waiting-on-owner/) | 3 | 0 |
|
||||
| [Workspaces & Panels](todo/workspaces-panels/) | 2 | 0 |
|
||||
| Theme | Open | Closed |
|
||||
| --- | --- | --- |
|
||||
| [Affordances](todo/affordances/) | 2 | 0 |
|
||||
| [Agents coordinating agents](todo/agent-coordination/) | 3 | 0 |
|
||||
| [Speaking into a session from the backend](todo/agent-delivery/) | 1 | 1 |
|
||||
| [Backburner](todo/backburner/) | 3 | 0 |
|
||||
| [Background jobs](todo/background-jobs/) | 2 | 1 |
|
||||
| [Chat interface](todo/chat/) | 1 | 0 |
|
||||
| [Code editor](todo/code-editor/) | 1 | 0 |
|
||||
| [Components — the shared UI layer](todo/components/) | 1 | 0 |
|
||||
| [File watchers](todo/file-watchers/) | 1 | 0 |
|
||||
| [Fleet — one command across every server](todo/fleet/) | 5 | 2 |
|
||||
| [Headscale](todo/headscale/) | 0 | 5 |
|
||||
| [Infra (alpha)](todo/infra/) | 7 | 0 |
|
||||
| [Known bugs](todo/known-bugs/) | 5 | 4 |
|
||||
| [Memory — one store instead of per-harness files](todo/memory/) | 1 | 0 |
|
||||
| [Mobile apps](todo/mobile-apps/) | 4 | 0 |
|
||||
| [Multi-user](todo/multi-user/) | 17 | 5 |
|
||||
| [Notifications](todo/notifications/) | 1 | 0 |
|
||||
| [Plugin ecosystem — before it opens to third parties (2026-08-18)](todo/plugin-ecosystem/) | 3 | 0 |
|
||||
| [Plugin infrastructure](todo/plugin-infrastructure/) | 2 | 0 |
|
||||
| [Plugin repos — what writing their READMEs turned up](todo/plugin-repos/) | 10 | 9 |
|
||||
| [Plugin system — gaps the email extraction found (2026-08-18)](todo/plugin-system/) | 3 | 0 |
|
||||
| [Prompt library](todo/prompt-library/) | 1 | 0 |
|
||||
| [SendOff](todo/sendoff/) | 1 | 0 |
|
||||
| [Settings — portable, and readable by an agent](todo/settings/) | 2 | 0 |
|
||||
| [Shell — spotlight and the dock](todo/shell/) | 4 | 1 |
|
||||
| [Sub-agents — agents spawning agents](todo/sub-agents/) | 0 | 2 |
|
||||
| [Sudo terminal modal](todo/sudo-terminal-modal/) | 1 | 0 |
|
||||
| [Tooling — the list about the list](todo/tooling/) | 0 | 1 |
|
||||
| [Transmission](todo/transmission/) | 6 | 0 |
|
||||
| [URL state — routing, query strings and selection](todo/url-state/) | 4 | 0 |
|
||||
| [Waiting on the owner to run it](todo/waiting-on-owner/) | 3 | 0 |
|
||||
| [Workspaces & Panels](todo/workspaces-panels/) | 2 | 0 |
|
||||
|
||||
## Records — no items, kept for the reasoning
|
||||
|
||||
|
||||
@@ -409,10 +409,26 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
break;
|
||||
|
||||
case 'session:claude':
|
||||
// The turn has only just begun and the conversation is already addressable. Everything that makes
|
||||
// a refresh survivable hangs off this: the URL is what the reattach handshake sends, so writing it
|
||||
// here rather than at `result` is the difference between "refresh mid-turn and lose the turn" and
|
||||
// "refresh mid-turn and watch it carry on".
|
||||
/*
|
||||
A turn is starting, and THIS TAB MAY NOT BE THE ONE THAT STARTED IT.
|
||||
|
||||
`isGenerating` used to be set in exactly three places — `sendPrompt`, and the two reconnect
|
||||
handshakes — so it answered "am I waiting for a reply I asked for", not "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 or another device driving the same session.
|
||||
|
||||
The tab still rendered the work, because the events arrive either way. But `buildRows` reads
|
||||
`isGenerating` to decide whether the last turn is finished, so with it false a live turn is
|
||||
`complete`, and the moment it sat on a completed tool call with nothing said after it the reader
|
||||
got "Turn finished · no closing message" — over an agent that carried on a second later. It also
|
||||
left the composer enabled mid-turn, and a message typed then was not marked `injected`, which
|
||||
splits the live turn: the precise failure `injected` exists to prevent (see types.ts).
|
||||
|
||||
Marked here rather than on the first tool call because this is the harness's own turn-start
|
||||
signal, and it is the only one that arrives before any work does.
|
||||
*/
|
||||
setIsGenerating(true);
|
||||
setHasStarted(true);
|
||||
claudeSessionIdRef.current = msg.claudeSessionId;
|
||||
setClaudeSessionId(msg.claudeSessionId);
|
||||
if (replaceUrl) writePermalink(msg.claudeSessionId);
|
||||
@@ -449,6 +465,9 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
}
|
||||
|
||||
case 'tool:start': {
|
||||
// Belt and braces for a turn whose `session:claude` this tab missed — an event that is NOT durable,
|
||||
// so a socket that connects mid-turn never receives one. A tool cannot start outside a turn.
|
||||
setIsGenerating(true);
|
||||
toolCallsInTurnRef.current = true;
|
||||
const call: ChatMessage = {
|
||||
role: 'tool',
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: known-bugs/a-turn-this-tab-did-not-start-reads-as-finished
|
||||
title: 'A turn this tab did not start reads as finished — "no closing message" over a working agent'
|
||||
theme: known-bugs
|
||||
status: done
|
||||
created: 2026-08-25
|
||||
date_source: stated
|
||||
order: 6
|
||||
---
|
||||
|
||||
**"Turn finished · no closing message" appears while the agent is still working, and output resumes a
|
||||
moment later.** Reported by the owner 2026-08-25 with a screenshot: the session list shows Platform Harry
|
||||
`generating`, and Platform Harry's own panel shows the banner under two completed Bash calls. He reports it
|
||||
happens often.
|
||||
|
||||
**Not the same bug as the two below it, and not a race.** `isGenerating` was set in exactly three places:
|
||||
`sendPrompt`, and the two reconnect handshakes (`sync:live`, `sync:messages`). 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 or another device driving the same session, or simply a turn that started before this tab was looking.
|
||||
|
||||
The tab still renders the work, because the events arrive regardless. But `buildRows` reads `isGenerating`
|
||||
to decide whether the last turn is finished (`generatingNow = isLast && isGenerating`), so with it false a
|
||||
live turn is `complete` — and the moment that turn sits on a completed tool call with nothing said after
|
||||
it, the banner fires. Then the next event arrives and the conversation carries on, which is exactly what
|
||||
"still outputting in a bit" is.
|
||||
|
||||
Two further consequences of the same gap, both visible in the screenshot: the composer stays **enabled**
|
||||
during someone else's turn, and a message typed then is not marked `injected` — which splits the live turn
|
||||
and prints the banner a second way, the precise failure `injected` was added to prevent (`types.ts`).
|
||||
|
||||
**Ruled out on the way, with evidence rather than reading.** A silent turn ending would earn the banner
|
||||
honestly, so that was measured first: across every durable event ever recorded, **667 of 673 turns end with
|
||||
`assistant:text` immediately before `result`**. Genuine silent endings are ~1% and cannot account for "a lot
|
||||
of times". A stale `sync:live` was the other candidate and is real but rare — correlating every attach in
|
||||
the officer log against the agent's own subsequent output, 15 of 549 "not generating" attaches were
|
||||
contradicted, and only **one** provably so (the first event after it was a `tool:result`, so a tool was
|
||||
already in flight when officer answered).
|
||||
|
||||
---
|
||||
|
||||
**Fixed 2026-08-25.** `session:claude` — the harness's own turn-start signal, and the only one that arrives
|
||||
before any work does — now sets `isGenerating`. `tool:start` sets it too, as a fallback: `session:claude` is
|
||||
not durable, so a socket that connects mid-turn never receives one, and a tool cannot start outside a turn.
|
||||
|
||||
Residual, and left alone deliberately: `sync:live` can still report `isGenerating: false` for a turn that is
|
||||
running (the one proven case above, `sidecar.isClaudeGenerating` answering for an adopted session). The
|
||||
`tool:start` fallback now corrects that on the next event rather than leaving the turn wrongly settled, so
|
||||
the symptom is covered even where the answer is not. Fixing the answer itself belongs with
|
||||
[[known-bugs/refresh-mid-tool-call-renders-the-turn-folded]], which is the same seam.
|
||||
@@ -43,3 +43,17 @@ Possibly related, same neighbourhood (refresh + a live turn + folding), not conf
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user