You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On the pinned Bun (1.3.14), node:http does not emit ServerResponse "close" when the client disconnects (oven-sh/bun#14697). @effect/platform-node interrupts the request fiber from exactly that event, so when a TUI drops its /api/event SSE connection the server side event stream never gets released. It keeps writing heartbeats into a dead socket, which is the state captured in the issue: server alive, listener open, health unresponsive, one core pinned in the native write path.
The fix is the bridge suggested in the issue: on Bun only, destroy the response from the request "aborted" event, which makes Bun emit the missing "close" so Effect can interrupt the stream and release its event subscription. The helper lives in core and is applied to both places we create a node:http server, the V1 server layer and the V2 serve command.
How did you verify your code works?
Added a test that opens an SSE response, aborts the client, and asserts the response "close" event fires. Without the bridge the event never fires on Bun 1.3.14 (verified with a copy of the test against a raw createServer), with the bridge it fires immediately. A second test confirms normally completed responses are not destroyed. bun test test/util/http-server.test.ts in packages/core: 2 pass. Typecheck passes for core, cli, and opencode.
AI code review — automated review for reference, author can ignore or act on any point.
Well-scoped workaround for a nasty class of leak — hooking "aborted" and destroying the response to force Bun's missing "close" is the least invasive fix available, the comment citing the upstream issue (packages/core/src/util/http-server.ts:30-37) is much appreciated, and the process.versions.bun gate keeps Node/Electron behavior byte-identical. Both production listeners are wired (packages/cli/src/commands/handlers/serve.ts:43, packages/opencode/src/server/server.ts:201), and the tests use real sockets including the negative case (packages/core/test/util/http-server.test.ts:68-110). Two notes:
Residual leak for silent disconnects."aborted" fires on an explicit client teardown of the request, but a peer that vanishes without FIN/RST (laptop sleep, NAT drop) emits nothing, so those SSE streams still accumulate until the next response.write() fails. Periodic SSE heartbeats would self-heal this; otherwise worth adding one line to the doc comment acknowledging the bridge only covers the explicit-abort case, so future readers don't assume it's total.
Coverage check on other servers.packages/stats/server/src/server.ts also builds a node:http server that runs under whatever runtime hosts it — if any of its routes are long-lived/streaming, it needs bridgeClientDisconnect too; if everything is short-request/response, fine as-is. (The WSL sidecar uses node:net, so it's unaffected.)
Minor: the writableEnded/destroyed check at http-server.ts:42 has an inherent TOCTOU window against concurrent end(), but destroying an ended response is harmless — no action needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #36311
Type of change
What does this PR do?
On the pinned Bun (1.3.14),
node:httpdoes not emitServerResponse"close" when the client disconnects (oven-sh/bun#14697).@effect/platform-nodeinterrupts the request fiber from exactly that event, so when a TUI drops its/api/eventSSE connection the server side event stream never gets released. It keeps writing heartbeats into a dead socket, which is the state captured in the issue: server alive, listener open, health unresponsive, one core pinned in the native write path.The fix is the bridge suggested in the issue: on Bun only, destroy the response from the request "aborted" event, which makes Bun emit the missing "close" so Effect can interrupt the stream and release its event subscription. The helper lives in core and is applied to both places we create a
node:httpserver, the V1 server layer and the V2servecommand.How did you verify your code works?
Added a test that opens an SSE response, aborts the client, and asserts the response "close" event fires. Without the bridge the event never fires on Bun 1.3.14 (verified with a copy of the test against a raw
createServer), with the bridge it fires immediately. A second test confirms normally completed responses are not destroyed.bun test test/util/http-server.test.tsin packages/core: 2 pass. Typecheck passes for core, cli, and opencode.Screenshots / recordings
Not a UI change.
Checklist