🐛 Fix `format_sse_event` dropping the event terminator when no field is set by SAURABHSALVE · Pull Request #16265 · fastapi/fastapi · GitHub
Skip to content

🐛 Fix format_sse_event dropping the event terminator when no field is set - #16265

Closed
SAURABHSALVE wants to merge 1 commit into
fastapi:masterfrom
SAURABHSALVE:fix-sse-empty-event-terminator
Closed

SAURABHSALVE wants to merge 1 commit into
fastapi:masterfrom
SAURABHSALVE:fix-sse-empty-event-terminator

Conversation

@SAURABHSALVE

Copy link
Copy Markdown
Contributor

Bug

format_sse_event() documents that:

The result always ends with \n\n (the event terminator).

It doesn't, when no field is set. The terminator is built by appending two empty entries to the list of field lines and then joining them with \n:

lines.append("")
lines.append("")
return "\n".join(lines).encode("utf-8")

When lines is empty the join produces a single "\n" instead of "\n\n":

>>> from fastapi.sse import format_sse_event
>>> format_sse_event()
b'\n'                # expected b'\n\n'
>>> format_sse_event(data_str='"x"')
b'data: "x"\n\n'     # correct

This is reachable from user code by yielding an empty ServerSentEvent() from an SSE path operation:

@app.get("/stream", response_class=EventSourceResponse)
async def stream():
    yield ServerSentEvent(data="first")
    yield ServerSentEvent()
    yield ServerSentEvent(data="second")

The empty event is emitted as a bare \n rather than a properly terminated (empty) event:

>>> # before
'data: "first"\n\n' '\n'   'data: "second"\n\n'
>>> # after
'data: "first"\n\n' '\n\n' 'data: "second"\n\n'

Fix

Append the terminator to the joined field lines instead of adding it as two list entries, so it is emitted unconditionally.

Output is byte-for-byte unchanged for every event that sets at least one field (data, event, id, retry or comment) — the only behavior change is the previously unterminated empty event.

Tests

Two tests added to tests/test_sse.py, both of which fail on master:

  • test_format_sse_event_without_fields_is_terminated — unit level.
  • test_empty_server_sent_event_is_terminated — end to end through an SSE path operation.

…is set

`format_sse_event()` built the terminating blank line by appending two empty
entries to the list of field lines before joining them with `\n`. When no
field was set the list was empty, so the join produced a single `"\n"` instead
of the `"\n\n"` terminator the function documents.

This is reachable from user code by yielding an empty `ServerSentEvent()` from
an SSE *path operation*.

Append the terminator to the joined field lines instead, so it is always
emitted. Output for every event that sets at least one field is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codspeed

codspeed Bot commented Aug 28, 2026

Copy link
Copy Markdown

@SAURABHSALVE

Copy link
Copy Markdown
Contributor Author

Hi! Could you please add the appropriate label to this PR so that the full set of required checks can run? Thank you! @YuriiMotov

@03musab

03musab commented Aug 28, 2026

Copy link
Copy Markdown

Great fix! Clear root cause analysis and clean fix for ormat_sse_event(). The string concatenation \n.join(lines) + \n\n cleanly guarantees event termination even when lines is empty. Tests cover both unit and integration levels. LGTM!

@Shriprasad-P Shriprasad-P left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have taken a look at the empty event scenario on master and have found that format_sse_event() is currently returning only b”\n” instead of the expected b”\n\n”. This change should fix that behaviour while still keeping the byte output for events that have data, event, id, retry or comment fields. I have also checked the whole SSE stream from start to finish with an empty ServerSentEvent() between two regular events, and everything is still framed correctly. The new regression tests that were added fail on master but pass with this change, and I have not found any problems with the formatter change.

Shriprasad-P

This comment was marked as spam.

Shriprasad-P

This comment was marked as spam.

@YuriiMotov

YuriiMotov commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

@YuriiMotov YuriiMotov closed this Sep 14, 2026
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.

4 participants