Skip to content

Build1 publisher3 min readPublished

A session that read "finished" and "still executing" was a slow queue, not a dropped handshake

One developer traced an hour-long Claude Code stall to 855 KB of streaming events for a single response. The tell was that output arrived late and in order, not missing.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying A session that read "finished" and "still executing" was a slow queue, not a dropped handshake
Generated illustration

What happened

  • On the night of August 14, Claude Code was planning an eight-day travel itinerary (not code) in the VS Code extension, running against a remote box where the author's sessions live.
  • Just past midnight the author's phone buzzed: the turn had finished and Claude was waiting for approval of its plan. The notification came from claude-code-notify, a hook the author wrote so a long turn, or one waiting on input, would not pass unnoticed; before it existed nothing notified at all.
  • When the author switched to the window, the session tab said the turn was still executing.
  • Both indicators were right, and the hour between them was the bug.
  • The extension was neither frozen nor empty: every so often it nudged a little more text into view, and the text was old, stale by the time it reached the screen.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

On the night of August 14, a Claude Code session drafting an eight-day travel itinerary in the VS Code extension against a remote box reported two states at once: a self-written notification hook said the turn had finished and was waiting for plan approval, while the session tab said the turn was still executing [1][2][3]. According to the developer's write-up, both indicators were right and the roughly one hour between them was the bug [4].

The useful part is the triage, which cost nothing. The extension was neither frozen nor empty; every so often it pushed a little more text into view, and the text was already stale by the time it rendered [5]. That single observation separates two failure classes. A lost handshake drops events: the session looks truncated and the missing pieces never arrive [6]. A queue does the opposite, delivering everything in order and late [7]. What the author was looking at was a delivery schedule, not a delivery failure.

The process state agreed. The stalled session was alive but asleep rather than spinning, with negligible CPU, zero network connections and zero child processes [8]. A turn waiting on a model holds a socket open; a turn running a command has children [9]. This one had neither, which points at a local wait, the way a program waits on a pipe [10].

The transcript was more precise. Claude Code journals each session as JSON lines, and the last line that night was a complete assistant message, usage accounted for and stop reason recorded, ending in the tool call that asked for plan approval, with nothing after it [11]. The CLI had finished producing its question and never heard the turn end [12]. The tool-call id was in the third-party provider's format rather than Anthropic's, which identified the road the request took [13].

That road was claude-code-router, self-hosted, pointed that night at a Zhipu GLM coding plan [14]. The router log answered the upstream question directly: the turn's request completed in 1.7 seconds, HTTP 200, no retries, no credential throttling, inside a surrounding hour of 186 requests that all returned 200 [15][16]. Upstream had finished long before anyone started asking, which pinned the stall to the segment after the CLI and before the screen [17].

A three-cell matrix closed it. Terminal plus router: fine. Extension plus the official API: fine. Extension plus router: stuck [18]. Neither component is the variable; the shape of the stream between them is.

The captured response bodies gave that shape two numbers: close to one streaming event per token at roughly 135 bytes each, totalling 855 KB of server-sent events for a single response, of which about 99% were thinking deltas with thinking mode on [19][20]. That works out to somewhere around 6,300 to 6,500 events for one answer [21]. The official Anthropic API merges many tokens into each delta, putting its event rate one to two orders of magnitude lower [22]. Per event, the extension parses JSON, posts a message across the Remote-SSH tunnel into the webview, and re-renders the whole conversation, which is not cheap on a long session [23]. The published excerpt breaks off before naming a fix.

Two caveats worth carrying. The author has since reduced body capture to failures only, so that night's numbers are a record rather than a measurement anyone can rerun [24]. And the arithmetic on the two clocks is stark: 1.7 seconds of model time against roughly an hour of render lag, a factor of about 2,000 [25].

Watch whether SSE event granularity gets treated as a compatibility surface rather than an implementation detail, since a renderer that assumes coarse deltas will queue on a provider that emits fine ones. If you run a router in front of a GUI client, decide now what you log, because the evidence that made this diagnosable is exactly the evidence that gets turned off for disk reasons.

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories