Disclaimer: this is a personal, local-only experiment, not a tested production finding and not a claim about any specific AI vendor or product. Everything here runs on my own machine against a small server I wrote myself. Think of it as a rough idea I worked through with some spare time and a token budget, worth discussing, not a verdict on anything in production.
Picture the order screen above the counter at a busy coffee shop. Your name lights up the instant the barista starts on your order, "Sam, in progress." But the register only logs the sale once the receipt printer finishes, and if that printer jams right after your drink is handed over, you're standing there with a finished coffee while the books say nothing was ever sold.
I found the same gap in AI chat streaming, so I built a small lab to check it. An AI client can render a complete, convincing response to the user while its own billing pipeline records absolutely nothing. No crash. Sometimes not even an error message.
TL;DR: in a streaming AI client where one part renders text and a separate part records billing, a dropped or corrupted frame between the last chunk of text and the usage frame can leave the user with a full response while billing logs zero. No crash, no exception in some cases. Proof of concept, three trigger conditions, and a fix, all local and reproducible: github.com/glatinone/streamblind-poc
Why this would matter if it showed up somewhere real
I want to be careful here: I have not seen this in any live product, and this lab does not prove any real system is affected. But if a similar pattern existed somewhere in production, the categories of impact are worth naming:
- Usage-based billing could under-count what a user actually received.
- A quota or rate limit that relies on the same billing signal could be bypassed.
- An audit log built on that signal would not be trustworthy for compliance reviews.
None of that is demonstrated against any real service in this post. It is the reason the pattern seemed worth writing up rather than shrugging off.
Where the two pipelines split
Streaming APIs used by AI chat products typically send a response as a sequence of small events over one connection: a start marker, a run of text fragments, a "content is done" marker, a usage frame, and a final stop marker.
A typical client splits handling of that sequence into two jobs that run more or less independently. One renders every text fragment to the screen the instant it arrives, so the UI feels alive. The other waits for the one, final usage frame near the end of the stream, and only then writes down what the response actually cost. In the lab code these are called the render pipeline and the telemetry pipeline (the source also calls the second one the "Administrative pipeline," same thing).
Here's that split, and the exact seam where things can go wrong:
The render side has already finished its job by the time the connection dies. It has no reason to undo anything, because from where it's standing, nothing went wrong. The telemetry side simply never gets to speak.
Still a local lab, still an experiment I ran on my own machine. Nothing below is a claim about a real product.
Breaking the seam three ways
The lab includes a small SSE server, built from nothing but Python's standard socket library, with three ways to trigger this. I'm calling the underlying bug class Asynchronous Telemetry Blindness: a client renders output fully while the pipeline meant to account for that output never catches up, and in some cases never even notices.
Vector A: a single corrupted byte
This one doesn't need a dropped connection at all. Every frame arrives, in the right order, including the final usage frame, except that one frame has a single invalid byte hidden inside its JSON body.
# api_server.py
malformed = (
'{"type": "message_delta", "delta": {"stop_reason": "end_turn", '
'"stop_sequence": null}, "usage": {"output_tokens": \x00 42}}'
)
Every text fragment before this point parses fine and renders normally. Then the one frame carrying the token count fails to parse, and only the telemetry code notices, because only the telemetry code ever reads that frame.
Vector B: a hard reset after the text is done
Here the text finishes normally and the server tears down the connection with a hard reset right after, before the usage frame ships. This one does raise a real exception on the client (ConnectionResetError), so at least there's a signal to catch, if the code is written to catch it and treat it as "response not fully accounted for" rather than just logging and moving on.
Vector C: a disconnect that raises nothing
This is the one that worries me more, because it needs no malformed data and no reset, just an ordinary network event: a proxy giving up on a slow connection and closing it cleanly.
In Python, a clean disconnect shows up as recv() returning empty bytes, with no exception raised:
# client_app.py, inside the network read loop
chunk = sock.recv(4096)
if not chunk:
# A clean disconnect lands here. Nothing raises.
# At the socket level this looks identical to a server
# that simply finished sending on purpose.
break
Most error handling is built around catching exceptions. This slips past that entirely, because there's nothing to catch. The stream just stops, and unless the client explicitly checks whether it actually reached the last expected message, it has no way of knowing anything went wrong.
The fix: speculative rendering with hard rollback
The obvious fix is to buffer everything and show nothing until the whole response is verified. That's safe, and it's also the fix that gets quietly reverted the first time someone sees the demo, because it brings back the wall of silence that streaming was supposed to remove.
A better answer keeps the live typing, but pairs it with a strict rule: text can appear on screen right away, as long as there's a guaranteed, instant way to take it back if the stream never reaches a fully verified end. Here's the core of that, from secure_client_app.py. Text still prints immediately as it arrives:
elif event == "content_block_delta":
if self.state != StreamState.IN_CONTENT:
self._fail(f"content_block_delta outside IN_CONTENT state ({self.state.name})")
text = obj["delta"]["text"]
# printed immediately, real streaming UX
sys.stdout.write(text)
sys.stdout.flush()
self._printed_segments.append(text)
self._append_history_text(text)
But every printed character is tracked, and a record of that "provisional" text is kept on disk alongside it. If the stream ever fails to reach a verified end, one function runs immediately, from the exact same code path that caught the failure:
def rollback(self, reason: str):
total_chars = sum(len(s) for s in self._printed_segments)
if total_chars:
sys.stdout.write(ANSI_CURSOR_TO_START + ANSI_CLEAR_LINE)
sys.stdout.flush()
history = read_history()
if self._history_index is not None and self._history_index < len(history):
del history[self._history_index]
write_history(history)
write_state({
"output_tokens": 0,
"stop_reason": None,
"last_update": f"REJECTED: {reason}",
"admin_pipeline_status": "rejected",
})
print(f"[INTEGRITY ALERT] stream rejected mid render, output rolled back. "
f"Reason: {reason}", file=sys.stderr)
It works like an undo button that's always armed. The response is allowed to type out on screen right away, the same way that ticker lights up your name before your drink is done. But the moment the handshake fails, the console is wiped, the provisional entry in the local history is deleted outright, and the record is written down as explicitly rejected instead of being left blank and ambiguous.
Verification
An automated test file in the lab reruns all three vectors against both the naive client and the fixed one, checking the files actually written to disk rather than trusting whatever the terminal happened to show. All twenty three checks pass, every time, against a freshly started server.
Try it yourself, three commands, no dependencies, no API keys:
git clone https://github.com/glatinone/streamblind-poc
cd streamblind-poc/fable_telemetry_lab
python run_exploit_test.py
That's the real fix here: not turning off the feature people actually want, but making sure it can be undone instantly and cleanly the moment something goes wrong. A smooth looking result on screen is not the same thing as a correctly recorded one.
One more reminder before you go: this is a local, first-pass experiment I put together on limited time, not a hardened tool and not evidence that any production system is broken. If you try it and find something interesting (or find a hole in my reasoning), that's exactly the kind of feedback I'm hoping for.
Everything is open source and runs locally: github.com/glatinone/streamblind-poc. Two things I'd genuinely like the comments on:
- In your streaming client, which side actually has the authority to say a response was "delivered," the renderer or the telemetry writer?
- Have you seen anything like this in a real system? How would you have caught it?
Related reading
If this was interesting, a couple of other things I've written on AI architecture failure modes:






Top comments (0)