From 011113b7e125622fb3af617c84a13484fbcfc94b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 06:06:57 +0000 Subject: [PATCH] fix(e2e): keep preview server keep-alive sockets open longer than clients CI on main (run 310, commit 58e5b5d) failed one public-sharing scenario with "apiRequestContext.post: socket hang up" on its first request, with no server-side error. The same tree had passed E2E on the PR run minutes earlier, and the failing call is the first request of the test. That is the keep-alive race: Playwright's request context reuses idle sockets to `vite preview`, whose Node http server closes an idle socket after 5s. A request that lands on a socket the server is closing at that instant dies client-side. Raising the preview server's keepAliveTimeout to 65s (headersTimeout 66s, which Node requires to be larger) makes the client always the side that closes, so the race cannot occur. A tiny Vite plugin does this only for `vite preview`; production runs `node build` and is unaffected. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014u5WimmZvshm9RJSd1c6wx --- vite.config.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index f60789f..9207f9d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,13 +2,36 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { SvelteKitPWA } from '@vite-pwa/sveltekit'; import tailwindcss from '@tailwindcss/vite'; import istanbul from 'vite-plugin-istanbul'; -import { defineConfig } from 'vite'; +import { defineConfig, type Plugin } from 'vite'; + +/** + * The e2e suite drives `vite preview` and its API helpers reuse keep-alive + * sockets. Node's http server drops an idle keep-alive socket after 5s by + * default, and a request that lands on a socket the server is closing at that + * instant fails client-side with "socket hang up" (seen on main CI in + * public-sharing.spec). Keeping the server's idle timeout well above any + * client's means the client always closes first, so the race cannot happen. + */ +function previewKeepAlive(): Plugin { + return { + name: 'crumbs:preview-keep-alive', + configurePreviewServer({ httpServer }) { + // HTTP/2 servers have no keep-alive timeout; preview is HTTP/1 here. + if ('keepAliveTimeout' in httpServer) { + httpServer.keepAliveTimeout = 65_000; + // Node requires headersTimeout to exceed keepAliveTimeout. + httpServer.headersTimeout = 66_000; + } + } + }; +} export default defineConfig({ build: { chunkSizeWarningLimit: 850 }, plugins: [ + previewKeepAlive(), tailwindcss(), sveltekit(), ...(process.env.VITE_COVERAGE === 'true'