What Changed — The SSE Coalescing Middleware in claude-code-router
If you run Claude Code through a self-hosted proxy like claude-code-router (ccr), you might have hit a nasty stall: approval prompts sitting for an hour because the UI extension couldn't keep up with per-token SSE events. The fix is a new middleware that merges those events into chunks, and it's now available in ccr.
The middleware, sse-coalesce.cjs, sits between the model provider and the Claude Code CLI. It re-chunks content_block_delta events so the stream looks like what the official Anthropic API already sends—merged chunks that the VS Code extension digests without choking.
What It Means For You
If you're using a third-party provider (DeepSeek, Zhipu, etc.) through ccr, you're exposed to the same problem: providers emit one event per token, and the extension's per-event rendering cost (estimated at 250-400ms) turns a 200-token response into 85 events that take minutes to display. This middleware collapses those events, cutting a 200-token response from 85 events to 11—an 8x reduction.
The middleware is safe by design: it only merges events that are provably interchangeable—same block index, same delta type (text into text, thinking into thinking, partial JSON into partial JSON). It flushes the moment any other event appears (like content_block_start or message_stop), so protocol semantics are preserved. It also handles transport realities: removes stale content-length headers, bypasses itself if compression isn't identity, and respects backpressure from the downstream consumer.
Try It Now — Setup and Configuration
Get the middleware file: Place
sse-coalesce.cjsin a directory you control (e.g.,/data/.claude-code-router/). Keep it in your ops repo and bind-mount it read-only into the container.Load it into every node process: Add this to your ccr container's environment:
NODE_OPTIONS="--require /data/.claude-code-router/sse-coalesce.cjs"This ensures every node process (core server and gateway) loads the module at birth. The module must self-install at load time—don't just export an
install()function that never gets called.Configure the window: Set
CCR_SSE_COALESCE_MS(e.g.,40) to control how long the middleware waits before merging. Per-type overrides exist:CCR_SSE_COALESCE_THINKING_MS,_TEXT_MS,_INPUT_JSON_MS. A value of zero or less falls back to the global—not "off". SetCCR_SSE_DROP_PINGS=0to keep keep-alive pings (but dropping them is better to avoid flushing mid-thinking).Deploy changes: After editing the middleware, run
docker compose up -d --force-recreate—bind mounts don't trigger reloads, and--requireonly loads at process birth.Verify: Check the middleware's stats log (truncated at 256 KB) to see merge counts. Example: a 200-token response went from 85 events to 11.
Why This Works
The root cause was a slow reader (the extension) backing up a pipe. The middleware smooths the stream's shape without changing its meaning. It's a targeted fix that respects the protocol—unlike naive buffering that could balloon memory. The official Anthropic API already streams in chunks, so this makes third-party providers behave the same way.
Caveats and Lessons
- Don't patch
globalThis.fetch: ccr's gateway uses undici's dispatcher, not fetch. Patch the API actually in use. - Don't edit generated files: The core server overwrites the gateway's preload file on startup. Bring your own file at a path the core doesn't know.
- Backpressure matters: The middleware pauses when the consumer signals stop, preventing a balloon effect.
- Tune the window: A 40ms window buys only 3-5x reduction if the provider emits every 25-50ms. Adjust based on your stream's tempo.
This fix turned an hour-long stall into a three-minute display delay—a massive improvement. For most users, the default settings work; start with CCR_SSE_COALESCE_MS=40 and adjust based on your provider's streaming pattern.
Source: dev.to
[Updated 16 Aug via devto_claudecode]
The middleware’s rollout was harder than the design. [per dev.to] The author needed five attempts to get it running: patching globalThis.fetch failed because ccr's gateway uses undici's dispatcher, not fetch. Moving to the dispatcher layer worked but only in the gateway process—the core server, which handles some provider traffic (e.g., DeepSeek), remained unpatched. The fix required loading the middleware into every node process via NODE_OPTIONS="--require", ensuring both core and gateway load it at birth. This multi-process insight is critical for anyone deploying ccr in containers with separate processes.









