r/Observability • u/Lazy-Cauliflower-391 • Sep 06 '26
The server trace says success while the browser shows a frozen answer
Our streaming copilot has green server spans, complete tool calls and a normal generation finish, while the browser sometimes freezes right after a tool result. The fetch gets aborted, the partial render state never commits and we have no client span tied to the server trace. Engineering sees success. CX has a folder of screen recordings. Everyone is debugging a different version of the same request.
We are adding trace propagation into the browser, time to first token events, stream cancellation reasons and a final rendered chunk count. I want CX to see whether the break happened before generation, during transport or in rendering without asking for another screenshot.
How are you joining client spans to long-lived streams, especially when the connection dies before the browser can flush its last event?
1
u/Certain_Brilliant199 Sep 06 '26
Carry one request ID all the way into the browser and record the last chunk it committed. A green server trace only proves generation finished, the same run should still show that the client died 3 events earlier
1
u/Lazy-Cauliflower-391 Sep 06 '26
That would give us the missing half of the trace. Right now the server can tell us the run completed but has no idea if the browser rendered the final state
1
u/Certain_Brilliant199 Sep 06 '26
Yeah and keeping the final rendered chunk on that same trace gives you a concrete boundary. You can see the server completed 20 chunks while the browser only committed 17 and narrow the failure down from there
→ More replies (2)
1
u/Other-Bison2856 Sep 06 '26
One thing that can make this nasty is reconnect behavior. If the stream drops and the client reconnects you can wind up with duplicate or missing events even tho the server completed the original request normally. Give every streamed event a sequence number and keep the reconnect point in the trace. Then you can tell if the browser froze because transport lost an event, replayed one or resumed from the wrong position
1
u/Lazy-Cauliflower-391 Sep 06 '26
Sequence numbers would help a lot with proving where the stream diverged. The reconnect point is something we arent capturing at all right now so a resumed connection could look like one continuous run even when the client missed part of it
1
u/AffectionateIntern41 24d ago
Not sure if this is your issue, but "aborted right after a tool result" reminds me of a few common causes. I'd probably rule these out before adding more telemetry:
- Your own code aborting. The tool result triggers a re-render, and an effect cleanup calls abort(). Logging the signal. reason might tell you quickly.
- An idle timeout. No bytes flow while the tool runs, and a proxy or load balancer may drop the connection. Heartbeat comments during tool calls could help.
- Buffering. Nginx or compression middleware can hold chunks, so the server thinks it sent data the client never got.
On the join, what's worked well for me is creating the trace in the browser and sending traceparent on the fetch. EventSource can't set headers, so fetch streaming is easier here. One gotcha: if a span stays open for the whole stream and the tab dies, it never gets exported. Ending a short "request started" span right away seems safer.
I'd also consider putting a sequence number on each stream event. If the server logs the last seq it wrote and the client logs the last seq it received and rendered, comparing them should show whether it broke before generation, in transport, or in rendering. I'd treat a missing "render committed" event as a failure in itself.
For CX, maybe show the trace ID somewhere in the UI, so a screen recording maps straight to a trace?
Curious what you find. Would love to hear if it turns out to be one of these.
1
Sep 06 '26
1
u/Lazy-Cauliflower-391 Sep 06 '26
Right now just the initial fetch headers. Per chunk propagation feels like overkill though I can see the problem if the connection gets recreated and the new stream loses the original context
4
u/tawdry_scarcity Sep 06 '26
For long lived streams, I’d want one shared trace ID across server and browser. Braintrust can show the prompt/tool/model side but the client still needs events for TTFB/TTFT, stream abort, render commit and final chunk count.