Skip to content

gateway: the SSE tap renders every event, bytes included - #113

Merged
andre-merzky merged 1 commit into
develfrom
fix/gateway-sse-bytes
Aug 19, 2026
Merged

gateway: the SSE tap renders every event, bytes included#113
andre-merzky merged 1 commit into
develfrom
fix/gateway-sse-bytes

Conversation

@andre-merzky

Copy link
Copy Markdown
Member

One line plus a regression test.

Gateway._sse_frame was a bare json.dumps, but the event tap forwards whatever a plugin published, and plugin payloads are not required to be JSON. A bytes payload (a raw stream event from the digital-twin plugin, in the case that found this) raised TypeError: Object of type bytes is not JSON serializable per event — one dropped SSE notification and one tap callback failed log line each, 292 of them in a 45 s run — while every JSON-clean event flowed normally, so the feed looked healthy unless you were waiting for exactly those events.

The SSE feed is a monitoring tier: the invariant worth having is that it renders every event, not that every payload round-trips. Anything JSON cannot encode now falls back to str(). Consumers that want the payload itself have the plugin's own channel; the tap only has to say the event happened. A typed encoder (e.g. base64 for bytes) would let SSE consumers decode payloads client-side — deliberately out of scope for this fix, noted here in case it's ever wanted.

Verified: new test_sse_frame_survives_non_json_payloads (bytes render, JSON-clean payloads unchanged), plus the existing SSE suite — 4 passed locally. flake8 clean.

🤖 Generated with Claude Code

The tap forwards whatever a plugin published, and plugin payloads are
not required to be JSON.  A `bytes` payload -- a raw stream event, for
one -- killed the frame with `TypeError: Object of type bytes is not
JSON serializable`: one dropped notification per event, one logged `tap
callback failed` each, 292 of them in a 45s digital-twin run, and an
SSE consumer which sees nothing while the broker looks busy.

The SSE feed is a monitoring tier, so the invariant is that it renders
every event rather than that every payload round-trips: anything JSON
cannot encode now falls back to `str()`.  A consumer which wants the
payload itself still has the plugin's own channel; the tap only has to
say that the event happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andre-merzky
andre-merzky merged commit f8faf36 into devel Aug 19, 2026
5 checks passed
@andre-merzky
andre-merzky deleted the fix/gateway-sse-bytes branch August 19, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant