Build1 publisher3 min readPublished
One event per token: the fix went in the proxy, because that was the only box he owned
A coalescing middleware in claude-code-router merged streaming deltas back into chunks, unblocking control messages that had queued behind six thousand of them.
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

What happened
- Claude Code on the author's machine goes through claude-code-router (ccr), a self-hosted proxy that lets the same CLI point at the official Anthropic API or at third-party providers, observed through the VS Code extension over Remote-SSH.
- A previous piece, Two Clocks, Neither Lying, worked out why an approval prompt once sat for an hour: one streaming event per token, an extension that could not burn them down fast enough, and the CLI's own control messages queued behind six thousand deltas.
- The provider's streaming granularity was theirs, not the author's to change.
- The VS Code extension is closed source and not the author's to patch.
- The router sat between provider and extension and was the author's own container, compose file and rules.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
An approval prompt in Claude Code once sat for an hour because the CLI's own control messages were queued behind six thousand streaming deltas, produced one event per token, in front of a VS Code extension that could not burn them down fast enough [2]. The repair went into claude-code-router, a self-hosted proxy that lets the same CLI point at either the official Anthropic API or third-party providers [1], because of the three components implicated, it was the only one the author could change [6].
The other two addresses were closed. Provider granularity was the provider's [3]. The extension is closed source [4]. The router, by contrast, was his container, his compose file, his rules [5]. That framing set the target: the official Anthropic API already streams in merged chunks and the extension digests those fine, so the goal was never a faster extension but a stream shaped like the one the extension already handles [7].
The hard part is not merging. It is merging without changing what the stream means, since a stall costs an hour and a lying stream costs the session [8]. The rule the author settled on is narrow: merge consecutive content_block_delta events only when they carry string payloads for the same block index and the same delta type, text into text, thinking into thinking, partial JSON into partial JSON, and flush the moment any other event appears [9]. Same index matters because one assistant message interleaves blocks, and merging across an index change fuses two blocks that never existed [10]. content_block_start, content_block_stop, message_delta and message_stop carry protocol meaning, so they empty the window and pass through untouched, in order [11]. The middleware understands nothing about the conversation, which the author treats as the safety property rather than a limitation [12].
The tuning surface is one knob with exceptions. CCR_SSE_COALESCE_MS sets the window globally, and zero disables the middleware outright [13]. Per-type overrides followed, CCR_SSE_COALESCE_THINKING_MS, _TEXT_MS and _INPUT_JSON_MS, because thinking deltas can afford to wait and text is what a human watches appear [14]. A per-type value of zero or less falls back to the global instead of switching that type off, because a zero-width window has no timer to flush it and would strand data until the stream ended [15]. Keep-alive pings are dropped by default, restorable with CCR_SSE_DROP_PINGS=0, on the grounds that a ping arriving mid-thinking-run would flush the window exactly where the stream is densest [16].
Two transport beliefs break when you rewrite bodies. Content-length is removed, because a client honoring a stale header will truncate or hang and SSE travels chunked anyway [17]. For compression, the middleware requests accept-encoding: identity, and if a compressed response arrives regardless it bypasses itself and passes the stream through untouched [18]. And because the original bug was a slow reader backing up a pipe, eager buffering would simply move the balloon one floor up: the queue pauses when the downstream consumer signals stop and resumes when it signals go [19].
Watch the deployment half. Writing the middleware took an evening; getting it to actually run took five attempts [20]. Also watch the bypass path, since any provider that ignores the identity request returns you to the original stall.