diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0897871..1130395 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,11 @@ jobs: # reached CI without running the Action's own suite. - 'scripts/**' - 'action/**' + # A patched dependency is application behaviour, not metadata: + # `patches/wrangler@*.patch` is the only thing keeping `wrangler + # dev` alive under the e2e suite. Editing it must run the suite + # that proves it still works. + - 'patches/**' e2e: - *js - 'e2e/**' @@ -371,6 +376,12 @@ jobs: # The dmesg grep is the other half: a SIGKILL with no exit code is the OOM # killer, and that is a different fix from a crash. # + # It was neither. `code=1 signal=null`, no OOM kill recorded, 8 GB free -- + # `wrangler dev` exited on ONE failed proxied request, reporting it as + # `✘ [ERROR]` with an empty message. That bug is patched; see + # `pnpm-workspace.yaml`. This step stays because it is what made the answer + # readable from the job page, and the next death will be a different one. + # # ON PUBLISHING THIS LOG, since `playwright.config.ts` turns screenshots # off over exactly this concern: the e2e Worker holds no secret. Its # `MASTER_KEY` and every seeded value are committed constants in diff --git a/e2e/harness/processes.ts b/e2e/harness/processes.ts index 5174550..6dfa9d4 100644 --- a/e2e/harness/processes.ts +++ b/e2e/harness/processes.ts @@ -257,6 +257,17 @@ export async function startDevServer(options: { * So record the death where the evidence already is, with the two fields that * decide the diagnosis: a `signal` of SIGKILL is the OOM killer, a non-zero * `code` is workerd deciding to stop on its own. + * + * That death had ONE cause, and it is now fixed rather than watched for. A + * single proxied request that failed -- `Network connection lost.`, raised + * when a client disconnects mid-upload, which is what Playwright does every + * time it closes a page while a form POST is in flight -- was fatal to the + * whole dev server, and was reported as `✘ [ERROR]` with an empty message. + * `pnpm-workspace.yaml` carries the upstream patch and the full explanation. + * + * This listener stays regardless. It cost nothing, it is what turned seventy + * identical `ERR_CONNECTION_REFUSED` failures into one legible line, and the + * next reason the Worker dies will not be that one. */ let exited: { code: number | null; signal: NodeJS.Signals | null } | null = null; child.once("exit", (code, signal) => { diff --git a/patches/wrangler@4.121.0.patch b/patches/wrangler@4.121.0.patch new file mode 100644 index 0000000..ce368ec --- /dev/null +++ b/patches/wrangler@4.121.0.patch @@ -0,0 +1,28 @@ +diff --git a/wrangler-dist/cli.js b/wrangler-dist/cli.js +index 9345fc99a8922769799e1a3008feb34d83400b81..893aaf2e91ae6f00a7e1e2e5f6ff21b3bb68a2ee 100644 +--- a/wrangler-dist/cli.js ++++ b/wrangler-dist/cli.js +@@ -183957,7 +183957,10 @@ var init_dist12 = __esm({ + this.onPreviewTokenExpired(); + break; + case "error": +- this.emitErrorEvent("Error inside ProxyWorker", message.error); ++ logger3.error( ++ "Error proxying request to the local Worker:", ++ message.error.message ++ ); + break; + } + } +@@ -279576,7 +279579,10 @@ var init_ProxyController = __esm({ + this.emitPreviewTokenExpiredEvent(message.proxyData); + break; + case "error": +- this.emitErrorEvent("Error inside ProxyWorker", message.error); ++ logger2.error( ++ "Error proxying request to the local Worker:", ++ message.error.message ++ ); + break; + case "debug-log": + logger2.debug("[ProxyWorker]", ...message.args); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bda8cd..aa3d9e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ catalogs: specifier: 4.121.0 version: 4.121.0 +patchedDependencies: + wrangler@4.121.0: dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59 + importers: .: @@ -98,7 +101,7 @@ importers: version: 1.31.0(svelte@5.56.8) '@sveltejs/adapter-cloudflare': specifier: ^7.2.9 - version: 7.2.9(@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(wrangler@4.121.0) + version: 7.2.9(@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(wrangler@4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59)) '@sveltejs/kit': specifier: ^2.70.2 version: 2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) @@ -164,7 +167,7 @@ importers: version: 4.1.10(@types/node@26.2.0)(@vitest/browser-preview@4.1.10)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) wrangler: specifier: 'catalog:' - version: 4.121.0 + version: 4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59) packages/docs: dependencies: @@ -183,7 +186,7 @@ importers: version: 5.9.3 wrangler: specifier: 'catalog:' - version: 4.121.0 + version: 4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59) packages/mcp: dependencies: @@ -5223,7 +5226,7 @@ snapshots: esbuild: 0.28.1 miniflare: 5.20260804.1-alpha vitest: 4.1.10(@types/node@26.2.0)(@vitest/browser-preview@4.1.10)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) - wrangler: 4.121.0 + wrangler: 4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59) zod: 4.4.3 transitivePeerDependencies: - '@cloudflare/workers-types' @@ -6186,12 +6189,12 @@ snapshots: dependencies: acorn: 8.18.0 - '@sveltejs/adapter-cloudflare@7.2.9(@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(wrangler@4.121.0)': + '@sveltejs/adapter-cloudflare@7.2.9(@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(wrangler@4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59))': dependencies: '@cloudflare/workers-types': 4.20260702.1 '@sveltejs/kit': 2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) worktop: 0.8.0-next.18 - wrangler: 4.121.0 + wrangler: 4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59) '@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.8)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(svelte@5.56.8)(typescript@5.9.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: @@ -9460,7 +9463,7 @@ snapshots: mrmime: 2.0.1 regexparam: 3.0.0 - wrangler@4.121.0: + wrangler@4.121.0(patch_hash=dc57aac6e1eca9830c3fe2c5a73e24e39efec67cad8544a2fff4465b2b912b59): dependencies: '@cloudflare/kv-asset-handler': 0.5.0 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6eee83b..f0bc0e3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -107,3 +107,41 @@ catalog: vite-plus: 0.2.9 vitest: 4.1.10 wrangler: 4.121.0 + +# ----------------------------------------------------------------------------- +# One upstream bug, patched here until it ships. +# +# `wrangler dev` treats a SINGLE failed proxied request as fatal to the whole +# dev server. Every request the browser makes is forwarded by wrangler's own +# ProxyWorker to the Worker runtime; if that inner fetch REJECTS, the +# ProxyWorker reports it to the ProxyController, `DevEnv.handleErrorEvent` finds +# no matching non-fatal case, and re-emits it as a top-level `error`. The dev +# command is `await events.once(devEnv, "teardown")`, and Node's `events.once` +# rejects on `error` -- so the command throws and the process exits 1. +# +# It exits SILENTLY. The rejection crosses a Worker boundary, so the cause is a +# plain object rather than an Error; `castErrorCause` wraps it in `new Error()`, +# whose message is "". The entire report is `✘ [ERROR]` with nothing after it. +# +# The inner fetch rejects with `Network connection lost.` when a client +# disconnects while its request body is still being uploaded -- which a browser +# does routinely, e.g. Playwright closing a page while a form POST is in flight. +# That is a client event, not a server fault, and it killed `e2e` mid-run: +# `wrangler dev` died about twenty tests in and every remaining spec failed with +# `ERR_CONNECTION_REFUSED`, three attempts each, none of them naming the cause. +# Re-running the job "fixed" it because the race simply did not recur. +# +# The patch is upstream's own fix, applied to the shipped bundle: report the +# failure and keep serving. It is two call sites because wrangler bundles the +# ProxyController twice (its own, and the remote-bindings copy). +# +# issue https://github.com/cloudflare/workers-sdk/issues/15203 +# fix https://github.com/cloudflare/workers-sdk/pull/15207 +# +# REMOVE THIS -- both the patch file and the entry below -- as soon as a +# wrangler release contains the fix. A version bump that leaves it here fails +# the install rather than applying quietly, which is the intended prompt to go +# and check. +# ----------------------------------------------------------------------------- +patchedDependencies: + wrangler@4.121.0: patches/wrangler@4.121.0.patch