Dynamic execution enginestreamingSSEdiagnosis

A day of fixing SSE frame parsing

The same ten lines were rewritten again and again in 24 hours. The raw-passthrough version, the only one that lost nothing, was gone in under two hours — and the code kept changing because it lived inside a generated template string.

Dec 12, 20254 min

When we hooked up external model providers, we made one promise: whichever provider is used, the response a client receives has the same shape.

Provider SSE payloads differ in field names, nesting depth and termination signals. Keeping the promise means opening each event as the stream passes and rewriting it into our shape, and at that point this layer goes from a relay that hands things on to a parser that takes every event apart.

Between 4 and 5 August

Between 10:17 on 4 August 2025 and 10:01 the following morning, the code in one place changed five times.

TimeWhat happened
8/4 15:20Drop line-based reading; reassemble events from bytes with a buffer
8/4 15:37Remove the buffer. Pass chunks straight through
8/4 17:21Restore the buffer and introduce an SSE event regex
8/5 10:01Remove the regex. Split on "\n\n", forward only frames containing data:

Some mechanisms were added and then taken back out. The buffer lasted seventeen minutes; the regex lasted until the next morning.

Every commit message just says "improved", so none of them says what broke or how. Given the same stream, the four versions do this.

One SSE stream, four implementations
Input stream
data: a\n\ndata: b\n\ndata: c\n\n
↓
What actually came out
first'data: a\n\nda'
second'ta: b\n\nda'
third'ta: c\n\n'
실패The separator "\n\n" is two characters; the cut was taken four characters in. Every event eats the first two characters of the next one. Everything after the first is corrupted.

Of the four, the only one that loses nothing is raw passthrough, and it was gone in under two hours.

Why we didn't go back to passthrough

Why go back to parsing when a lossless version existed? The final implementation shows why. It pulls each event apart, remaps it into the gateway's common shape, filters out the termination sentinel, and blanks a field for certain event types.

Raw passthrough forwards the stream untouched, so it can't do any of that. And making differently-shaped providers look the same is the reason this layer exists.

The code lived inside a string

I think where the code lived is part of why it was touched so often in one day. The relay logic sat inside a string in a generation template, and the server used that template to stamp out a file for each provider.

stampsoverwritesGeneration templatea stringGenerated api.pyone per providerSub-appmountedRegenerate allmaintenance API

Hover a node to keep only what it connects to

So a bug in the template is already copied into every deployed provider. Fixing the template alone doesn't help. Every generated file has to be stamped again, which is why there's a maintenance endpoint that does only that.

It's also hard to step through in a debugger. Inside a string the editor doesn't check syntax, there's no completion, and there's one extra layer of escaping. Whether "\n\n" counts as two characters or four is exactly the confusion behind the 15:20 bug, and it came from that extra layer.

Where it stands now

Four months later the logic moved out of the template string into a shared function, and the generated files now just call it.

One loss was repaired too. Comment frames starting with a colon are forwarded untouched again, so heartbeats came back.

Frames carrying only event: and id: get dropped, though. The final implementation handles frames that have data and otherwise only checks for a comment, so a frame that is neither reaches nobody. That loss has been there since the morning of 5 August; it hasn't shown up because the providers attached today don't send frames like that.

All five commit messages just say "improved", so what broke and why it was reverted can only be found by running the code again. What I needed four months later was one line like "this cuts off two characters". There was also no way to check anything between deploys back then. A check that feeds in one stream and compares what comes out would have caught the 15:20 bug before it was committed.

End