Skip to content

fix: prevent SSE event loss in connectEvents onerror handler#122

Open
fallleave001 wants to merge 1 commit into
agegr:mainfrom
fallleave001:fix/sse-onerror-clean
Open

fix: prevent SSE event loss in connectEvents onerror handler#122
fallleave001 wants to merge 1 commit into
agegr:mainfrom
fallleave001:fix/sse-onerror-clean

Conversation

@fallleave001

Copy link
Copy Markdown

Problem

The connectEvents onerror handler unconditionally called settle() +
es.close() on every error, then manually reconnected after 1s. This
created a race condition:

  1. A recoverable network glitch triggers onerror
  2. settle() resolves the Promise immediately → handleSend proceeds
    to send the prompt
  3. es.close() kills the SSE connection → all events emitted during
    the 1s reconnect window (agent_start, message_*, prompt_done,
    agent_end) are lost
  4. The UI stays stuck in "waiting for model" until
    reconcileAgentState (15s polling) recovers

Fix

Distinguish between fatal errors (readyState === CLOSED, e.g. 404/500)
where manual reconnect is needed, and recoverable errors (CONNECTING)
where EventSource auto-reconnects on its own.

For recoverable errors we no longer settle() or close() — the 1.5s
timeout in the Promise constructor still ensures handleSend won't
hang. This eliminates the 1s event-loss window that was the root cause
of the stuck UI.

Testing

  • Recoverable error: simulate network glitch (offline/online in
    DevTools) during an active run → SSE auto-reconnects, no events lost,
    streaming continues
  • Fatal error: return 500 from events route → manual reconnect kicks
    in, UI recovers via reconcile polling if needed
  • Normal flow: new session + prompt → streaming works as expected

The old onerror handler unconditionally called settle() + es.close() on
every error, then manually reconnected after 1s. This created a race:

1. A recoverable network glitch triggers onerror
2. settle() resolves the Promise immediately -> handleSend proceeds to
   send the prompt
3. es.close() kills the SSE connection -> all events emitted during the
   1s reconnect window (agent_start, message_*, prompt_done, agent_end)
   are lost
4. The UI stays stuck in 'waiting for model' until reconcileAgentState
   (15s polling) recovers

The fix distinguishes between fatal errors (readyState === CLOSED, e.g.
404/500) where manual reconnect is needed, and recoverable errors
(CONNECTING) where EventSource auto-reconnects on its own. For
recoverable errors we no longer settle() or close() - the 1.5s timeout
in the Promise constructor still ensures handleSend won't hang.
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.

2 participants