Skip to content

fix(sse): prevent client-abort (aborted/ECONNRESET) from crashing the server - #11556

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
yxyxy:fix/client-abort-resilience
Aug 26, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
yxyxy:fix/client-abort-resilience

Conversation

@yxyxy

@yxyxy yxyxy commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

fix(sse): prevent client-abort (aborted/ECONNRESET) from crashing the server

Extracted from #11285 (per review feedback — this part is clean, self-contained, and wanted this cycle).

Summary

A client disconnect (aborted / ECONNRESET on the incoming socket) previously surfaced as an unhandled uncaughtException, crashing the dev and production HTTP server — any browser navigation / HMR / WebSocket disconnect could take the server down.

Changes

  • New shared guard module src/shared/utils/httpClientAbortGuard.mjs with isClientAbortError, shouldSwallowUncaught, attachRequestStreamGuards, installProcessCrashGuard.
  • Dev server integration (scripts/dev/run-next.mjs, re-export scripts/dev/httpClientAbortGuard.mjs).
  • Applied to sibling createServer call sites: apiBridgeServer, liveServer (WS), embedWsProxy.
  • Genuine server errors are still surfaced (crash semantics preserved — only client-abort signatures are swallowed).
  • Unit tests tests/unit/httpClientAbortGuard.test.mjs (8/8 passing).

Verification

  • node --test tests/unit/httpClientAbortGuard.test.mjs → 8/8 passing.
  • Manual: server survives client aborts (bcache disconnect, HMR failure, WebSocket closed before established) without uncaughtException: aborted.

Base

Targets release/v3.8.51 (this cycle). Rebased from the original release/v3.8.50 work.

Related: #11285 (full bundle, discussed as RFC in #11288), #11288 (org-layer RFC).

…/prod server

A browser closing the TCP socket during navigation/HMR/Back-Forward
cache (or nulling the request while the dashboard polls after login)
emits Error: aborted / ECONNRESET on the http.IncomingMessage. With no
'error' listener attached, Node re-throws it as an uncaughtException
that kills the whole server process — surfacing as a wall of
net::ERR_CONNECTION_REFUSED after login (see app.log uncaughtException: aborted).

- scripts/dev/httpClientAbortGuard.mjs: reusable guard that attaches
  req/res 'error' listeners (absorbing client-abort errors) and a
  process-level uncaughtException/unhandledRejection safety net that
  swallows benign aborts while preserving crash semantics for real errors.
- scripts/dev/run-next.mjs: install the guard in start() and attach
  per-request stream guards in the request listener.
- tests/unit/httpClientAbortGuard.test.mjs: TDD coverage for the exact
  abortIncoming crash signature + idempotency + crash-semantics preservation.
…, liveWS, embedWS)

Extends #fix-dev-server-aborted beyond the Next dev/prod server to the
other Node http servers that are equally exposed to a client aborting the
TCP socket (navigation/HMR/bfcache) while a response is in flight:

- src/lib/apiBridgeServer.ts — the OpenAI-compatible API bridge server.
- src/server/ws/liveServer.ts — the live-dashboard WebSocket daemon (20132).
- src/lib/services/embedWsProxy.ts — the embedded-service WS proxy (20131).

Each now calls attachRequestStreamGuards(req, res) inside its
http.createServer request listener, and installProcessCrashGuard() once
at start (idempotent process-level safety net).

The guard implementation is promoted to a single source of truth at
src/shared/utils/httpClientAbortGuard.mjs (importable from both the
Node-only dev server and the TypeScript servers; tsconfig allowJs:true).
scripts/dev/httpClientAbortGuard.mjs is now a pure re-export so the dev
server's existing import path is unchanged.

TDD: adds a test asserting the dev re-export is the SAME function objects
as the shared module (guards against future drift). Full guard suite stays
green (8/8).
@yxyxy
yxyxy requested a review from diegosouzapw as a code owner August 25, 2026 20:30
@diegosouzapw
diegosouzapw merged commit 2d185de into diegosouzapw:release/v3.8.51 Aug 26, 2026
3 checks passed
diegosouzapw pushed a commit that referenced this pull request Aug 29, 2026
…2042)

Real production incident (2026-08-29): the crash guard #11556 introduced defaulted its logger to `log ?? console` — console is an object, not a function, so a burst of client aborts (ECONNRESET) reaching the process-level guard threw TypeError inside the uncaughtException handler itself and killed the server, twice in three minutes. Fix: default to console.warn.bind(console).

Bug-injection round trip confirms the new test fails on the old default and passes on the fix. Existing guard suite stays green: 9/9 (verified together with the new test).
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… server (diegosouzapw#11556)

Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…egosouzapw#12042)

Real production incident (2026-08-29): the crash guard diegosouzapw#11556 introduced defaulted its logger to `log ?? console` — console is an object, not a function, so a burst of client aborts (ECONNRESET) reaching the process-level guard threw TypeError inside the uncaughtException handler itself and killed the server, twice in three minutes. Fix: default to console.warn.bind(console).

Bug-injection round trip confirms the new test fails on the old default and passes on the fix. Existing guard suite stays green: 9/9 (verified together with the new test).
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