[PR #916] [MERGED] fix(dev): hoist socket-error backstop to module top-level so lifecycle events can't remove it #947

Closed
opened 2026-05-06 13:11:01 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/916
Author: @james-elicx
Created: 4/26/2026
Status: Merged
Merged: 4/27/2026
Merged by: @james-elicx

Base: mainHead: claude/dev-socket-debug


📝 Commits (10+)

  • 95f00fc debug(dev): hoist socket-error backstop to module top-level + opt-in trace
  • 455a61b fix: address bonk review on dev socket-error backstop
  • ffc5684 fix(dev): re-hoist socket-error backstop to module load (configureServer too late)
  • 05bbaac refactor(dev): positive-gate socket backstop on argv[2]==='dev'
  • 18b4bb0 refactor(dev): gate socket backstop on Vite's command==='serve'
  • 4683211 fix(dev): narrow gate to !isPreview so vite preview skips install
  • 501dd95 fix: extend socket-error backstop to vinext start (Next.js parity)
  • 295099c fix(dev): auto-install socket-error backstop at module load
  • 90f7429 refactor: call installSocketErrorBackstop directly in index.ts
  • 8d044df fix: gate socket backstop on NODE_ENV to keep prerender ECONNRESET fatal

📊 Changes

6 files changed (+219 additions, -42 deletions)

View changed files

📝 packages/vinext/src/build/prerender.ts (+11 -4)
📝 packages/vinext/src/build/run-prerender.ts (+11 -0)
📝 packages/vinext/src/index.ts (+9 -38)
📝 packages/vinext/src/server/prod-server.ts (+10 -0)
packages/vinext/src/server/socket-error-backstop.ts (+130 -0)
tests/socket-error-backstop.test.ts (+48 -0)

📄 Description

Summary

Real fix for the residual #905 crashes that #913 didn't catch. Confirmed via the opt-in trace below.

#913 installed the uncaughtException handler inside configureServer and tore it down on httpServer 'close'. Vite emits close on dep re-optimization, full reloads, and other lifecycle events — leaving a window where the listener is absent when a stale stream errors. The exact pattern users were hitting:

GET /page 200 in 109ms
node:events:487
      throw er; // Unhandled 'error' event
Error: read ECONNRESET

Reproducer confirmed with the diagnostic flag from this PR:

$ VINEXT_DEBUG_SOCKET_ERRORS=1 vp dlx https://pkg.pr.new/vinext@916 dev
...
[vinext] absorbed uncaughtException ECONNRESET

The marker firing where the crash used to happen confirms the listener is now in place when the error fires.

Changes

  1. Hoist install from configureServer to module top-level, guarded by Symbol.for("vinext.devSocketErrorBackstop") to prevent double-install (e.g. when vinext is loaded by both a project and a sibling workspace package). No teardown — the listener lives for the process.

  2. Add opt-in VINEXT_DEBUG_SOCKET_ERRORS=1 trace that logs when the listener absorbs a peer-disconnect. Default off; useful for confirming the listener fires when users still see crashes in the field.

Filter codes (ECONNRESET / EPIPE / ECONNABORTED) and synchronous re-throw of non-peer-disconnect errors are unchanged from #913.

Refs

  • Refs #905 (residual crashes after #911 and #913)
  • Closes #915 (the EventEmitter.prototype.emit patch was the wrong direction — domain can replace prototype emit on its load, so that approach was fragile)

Test plan

  • vp check passes (1 pre-existing benchmarks Cannot find module 'vinext' error is unrelated)
  • Reproducer from #905: VINEXT_DEBUG_SOCKET_ERRORS=1 vp dev no longer crashes; [vinext] absorbed uncaughtException ECONNRESET printed where the crash used to be
  • Genuine programming errors still crash dev server with full stack (sync re-throw preserves Node default crash semantics)

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/916 **Author:** [@james-elicx](https://github.com/james-elicx) **Created:** 4/26/2026 **Status:** ✅ Merged **Merged:** 4/27/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `claude/dev-socket-debug` --- ### 📝 Commits (10+) - [`95f00fc`](https://github.com/cloudflare/vinext/commit/95f00fca8004f6e25b49b04975404d18b5d32d92) debug(dev): hoist socket-error backstop to module top-level + opt-in trace - [`455a61b`](https://github.com/cloudflare/vinext/commit/455a61bd98bb1e86ba63cfd0a87582a54cc046fe) fix: address bonk review on dev socket-error backstop - [`ffc5684`](https://github.com/cloudflare/vinext/commit/ffc5684ae934cad384ec2126cc9182ca114b169a) fix(dev): re-hoist socket-error backstop to module load (configureServer too late) - [`05bbaac`](https://github.com/cloudflare/vinext/commit/05bbaac342f2d7a26bd17c9705190a9c9d2cde8c) refactor(dev): positive-gate socket backstop on argv[2]==='dev' - [`18b4bb0`](https://github.com/cloudflare/vinext/commit/18b4bb0c28711d4a8c6911b49f46d38a14db945b) refactor(dev): gate socket backstop on Vite's command==='serve' - [`4683211`](https://github.com/cloudflare/vinext/commit/4683211b04fb3615758ae32302f59767daae01ae) fix(dev): narrow gate to !isPreview so vite preview skips install - [`501dd95`](https://github.com/cloudflare/vinext/commit/501dd95f6c1295ee0790e1be98d81d0950e76728) fix: extend socket-error backstop to vinext start (Next.js parity) - [`295099c`](https://github.com/cloudflare/vinext/commit/295099c70fae3d121cc3353ae8b804c05b5ae5e6) fix(dev): auto-install socket-error backstop at module load - [`90f7429`](https://github.com/cloudflare/vinext/commit/90f74295832a948b4d9fecae983706fd570c4b3c) refactor: call installSocketErrorBackstop directly in index.ts - [`8d044df`](https://github.com/cloudflare/vinext/commit/8d044dfab2b7dc645d656c7b575c0bef9f59b90f) fix: gate socket backstop on NODE_ENV to keep prerender ECONNRESET fatal ### 📊 Changes **6 files changed** (+219 additions, -42 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/build/prerender.ts` (+11 -4) 📝 `packages/vinext/src/build/run-prerender.ts` (+11 -0) 📝 `packages/vinext/src/index.ts` (+9 -38) 📝 `packages/vinext/src/server/prod-server.ts` (+10 -0) ➕ `packages/vinext/src/server/socket-error-backstop.ts` (+130 -0) ➕ `tests/socket-error-backstop.test.ts` (+48 -0) </details> ### 📄 Description ## Summary Real fix for the residual #905 crashes that #913 didn't catch. Confirmed via the opt-in trace below. #913 installed the `uncaughtException` handler inside `configureServer` and tore it down on `httpServer` `'close'`. Vite emits `close` on dep re-optimization, full reloads, and other lifecycle events — leaving a window where the listener is absent when a stale stream errors. The exact pattern users were hitting: ``` GET /page 200 in 109ms node:events:487 throw er; // Unhandled 'error' event Error: read ECONNRESET ``` Reproducer confirmed with the diagnostic flag from this PR: ``` $ VINEXT_DEBUG_SOCKET_ERRORS=1 vp dlx https://pkg.pr.new/vinext@916 dev ... [vinext] absorbed uncaughtException ECONNRESET ``` The marker firing where the crash used to happen confirms the listener is now in place when the error fires. ## Changes 1. **Hoist install from `configureServer` to module top-level**, guarded by `Symbol.for("vinext.devSocketErrorBackstop")` to prevent double-install (e.g. when vinext is loaded by both a project and a sibling workspace package). No teardown — the listener lives for the process. 2. **Add opt-in `VINEXT_DEBUG_SOCKET_ERRORS=1` trace** that logs when the listener absorbs a peer-disconnect. Default off; useful for confirming the listener fires when users still see crashes in the field. Filter codes (`ECONNRESET` / `EPIPE` / `ECONNABORTED`) and synchronous re-throw of non-peer-disconnect errors are unchanged from #913. ## Refs - Refs #905 (residual crashes after #911 and #913) - Closes #915 (the `EventEmitter.prototype.emit` patch was the wrong direction — domain can replace prototype emit on its load, so that approach was fragile) ## Test plan - [x] `vp check` passes (1 pre-existing benchmarks `Cannot find module 'vinext'` error is unrelated) - [x] Reproducer from #905: `VINEXT_DEBUG_SOCKET_ERRORS=1 vp dev` no longer crashes; `[vinext] absorbed uncaughtException ECONNRESET` printed where the crash used to be - [x] Genuine programming errors still crash dev server with full stack (sync re-throw preserves Node default crash semantics) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:01 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#947
No description provided.