Skip to content

fix(opencode): handle client disconnect in SSE event route writes#18538

Open
zaxbysauce wants to merge 1 commit intoanomalyco:devfrom
zaxbysauce:fix/sse-client-disconnect-handling-v2
Open

fix(opencode): handle client disconnect in SSE event route writes#18538
zaxbysauce wants to merge 1 commit intoanomalyco:devfrom
zaxbysauce:fix/sse-client-disconnect-handling-v2

Conversation

@zaxbysauce
Copy link

Issue for this PR

Closes #15149

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The /event and /global/event SSE endpoints use an AsyncQueue drained by a for await loop that calls stream.writeSSE(). When a client disconnects abruptly — or when rapid-fire bus events cause writeSSE backpressure — the thrown error propagates out of the for await loop unhandled. This corrupts server state, causing subsequent requests to fail with "Unexpected EOF" errors and killing active sessions.

This is the same bug as the original PR #15147, rebased onto the current dev branch where the SSE routes were refactored from inline server.ts handlers into separate routes/event.ts and routes/global.ts files using the AsyncQueue pattern.

The fix wraps each writeSSE call inside the for await drain loop in a try/catch. On failure, it logs the disconnection and returns cleanly, letting the finally block run stop() to clear the heartbeat interval and unsubscribe from the bus. Two files changed, identical pattern in both.

How did you verify your code works?

  • Traced the crash path from bash.ts (fire-and-forget ctx.metadata() on every stdout chunk) through Session.updatePart()Bus.publish(PartUpdated)AsyncQueue.push()stream.writeSSE() failure → unhandled throw → session death
  • Reproduced the crash by running a test suite that produces 40+ rapid bus events through the MCP bash tool, overwhelming the SSE drain loop
  • Confirmed the fix pattern matches the existing try/catch handling already used in the server.ts inline SSE handler from the original PR

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

Wrap writeSSE calls in try/catch within the for-await queue drain
loops in both event.ts and global.ts route handlers.

When a client disconnects abruptly (or writeSSE throws due to
backpressure), the unhandled error propagates out of the for-await
loop, corrupting server state and causing subsequent requests to
fail with 'Unexpected EOF' errors.

The fix catches writeSSE failures, logs the disconnection, and
returns cleanly — allowing the finally block to call stop() which
clears the heartbeat interval and unsubscribes from the bus.
@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

Related PR Found

PR #15147: fix(opencode): handle client disconnect in SSE stream writes
#15147

This is the original PR that addressed the same bug (client disconnect handling in SSE endpoints). The current PR (18538) is explicitly described as a rebase of that fix onto the refactored codebase where the SSE routes were moved from inline server.ts handlers into separate routes/event.ts and routes/global.ts files using the AsyncQueue pattern.

This is not a duplicate in the traditional sense—rather, PR #15147 is the predecessor fix that the current PR builds upon after the codebase refactoring.

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.

Server becomes unresponsive after external client disconnects during SSE streaming

1 participant