From 64e3a10a6523e51396f404318290c99785802ba5 Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Mon, 15 Jun 2026 19:36:20 +0530 Subject: [PATCH 1/5] fix(wrangler): remove stale --experimental-vm-modules flag and prevent silent exits Remove `--experimental-vm-modules` from the child process spawn args in `bin/wrangler.js`. This flag was deprecated when `vm.Module` became stable in Node.js v22, and `wrangler-dist/cli.js` makes zero use of VM Modules. The stale flag causes unexpected behaviour on Node.js v26, where users see wrangler silently exit with code 1 after printing the banner. Also wrap the `handleError()` call in a try-catch so that if `handleError()` itself throws, the original error message is written directly to stderr. Previously, `handleError()` exceptions were silently swallowed by the top-level `.catch()` in `cli.ts`, producing a silent exit with code 1. Fixes #14296 --- .changeset/fix-node-v26-silent-exit.md | 11 +++++++++++ packages/wrangler/bin/wrangler.js | 1 - packages/wrangler/src/index.ts | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-node-v26-silent-exit.md diff --git a/.changeset/fix-node-v26-silent-exit.md b/.changeset/fix-node-v26-silent-exit.md new file mode 100644 index 0000000000..d6546bec0d --- /dev/null +++ b/.changeset/fix-node-v26-silent-exit.md @@ -0,0 +1,11 @@ +--- +"wrangler": patch +--- + +fix: remove deprecated `--experimental-vm-modules` flag and prevent silent exit on unexpected errors + +`wrangler` was silently exiting with code 1 on Node.js v26 with no error message. Two changes address this: + +1. Remove `--experimental-vm-modules` from the child process spawn flags in `bin/wrangler.js`. This flag was deprecated when `vm.Module` became stable in Node.js v22, and the compiled `wrangler-dist/cli.js` does not use VM Modules. The stale flag could cause unexpected behaviour on Node.js v26. + +2. Wrap the `handleError()` call in a try-catch so that if `handleError()` itself throws, the original error message is written directly to stderr instead of being silently swallowed by the top-level `.catch()` in `cli.ts`. diff --git a/packages/wrangler/bin/wrangler.js b/packages/wrangler/bin/wrangler.js index 6877edf1a3..774ecc2ce6 100755 --- a/packages/wrangler/bin/wrangler.js +++ b/packages/wrangler/bin/wrangler.js @@ -25,7 +25,6 @@ Consider using a Node.js version manager such as https://volta.sh/ or https://gi process.execPath, [ "--no-warnings", - "--experimental-vm-modules", ...process.execArgv, path.join(__dirname, "../wrangler-dist/cli.js"), ...process.argv.slice(2), diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 6eadcb12d3..fdb3013016 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -2419,7 +2419,16 @@ export async function main(argv: string[]): Promise { if (dispatcher) { dispatchGenericCommandErrorEvent(dispatcher, startTime, e); } - await handleError(e, configArgs, argv); + try { + await handleError(e, configArgs, argv); + } catch { + // handleError itself threw before it could log the error. + // Fall back to raw stderr so the user always sees something. + const message = e instanceof Error ? e.message : String(e); + if (message) { + process.stderr.write(`\n${message}\n`); + } + } throw e; } } finally { From f5ef16cd71434948e3fbc2cbad78b311178cd608 Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Tue, 16 Jun 2026 10:19:26 +0530 Subject: [PATCH 2/5] address reviewer feedback on PR #14306 - Remove fix: prefix from changeset title (Devin) - Rewrite changeset body to focus on user-facing impact, not implementation details (Devin) - Update AGENTS.md to remove stale --experimental-vm-modules reference (Devin) - Capture handleError exception in catch block, include in stderr fallback (Copilot) - Use e.stack ?? e.message for more informative fallback output (Copilot) --- .changeset/fix-node-v26-silent-exit.md | 8 ++++---- packages/wrangler/AGENTS.md | 2 +- packages/wrangler/src/index.ts | 15 ++++++++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.changeset/fix-node-v26-silent-exit.md b/.changeset/fix-node-v26-silent-exit.md index d6546bec0d..2fb4312499 100644 --- a/.changeset/fix-node-v26-silent-exit.md +++ b/.changeset/fix-node-v26-silent-exit.md @@ -2,10 +2,10 @@ "wrangler": patch --- -fix: remove deprecated `--experimental-vm-modules` flag and prevent silent exit on unexpected errors +Remove deprecated `--experimental-vm-modules` flag and prevent silent exit on unexpected errors -`wrangler` was silently exiting with code 1 on Node.js v26 with no error message. Two changes address this: +`wrangler` was silently exiting with code 1 on Node.js v26 with no error message shown. This release fixes two independent issues that caused this behaviour: -1. Remove `--experimental-vm-modules` from the child process spawn flags in `bin/wrangler.js`. This flag was deprecated when `vm.Module` became stable in Node.js v22, and the compiled `wrangler-dist/cli.js` does not use VM Modules. The stale flag could cause unexpected behaviour on Node.js v26. +1. A stale Node.js flag that caused unexpected behaviour on Node.js v26 has been removed. -2. Wrap the `handleError()` call in a try-catch so that if `handleError()` itself throws, the original error message is written directly to stderr instead of being silently swallowed by the top-level `.catch()` in `cli.ts`. +2. If an error occurs in a situation where the normal error reporting path itself fails, `wrangler` now always prints the original error to stderr so the cause is visible rather than silently disappearing. diff --git a/packages/wrangler/AGENTS.md b/packages/wrangler/AGENTS.md index d0cfa73d23..d47c4d3e7f 100644 --- a/packages/wrangler/AGENTS.md +++ b/packages/wrangler/AGENTS.md @@ -11,7 +11,7 @@ Main CLI for Cloudflare Workers. ~2k-line yargs command tree in `src/index.ts`. - `src/` — CLI source - `src/__tests__/` — Unit tests, helpers in `src/__tests__/helpers/` - `e2e/` — E2E tests, requires Cloudflare credentials -- `bin/wrangler.js` — Shim that spawns Node with `--experimental-vm-modules` +- `bin/wrangler.js` — Shim that spawns Node to run `wrangler-dist/cli.js`, forwarding stdio and IPC - `bin/cf-wrangler.js` — `cf-wrangler` delegate entrypoint. Owns verb dispatch and argv parsing (`parseCfWranglerArgs`, `parseCfWranglerBuildArgs`); hands off to `runCfWranglerDev` / `runCfWranglerBuild` from `wrangler-dist/cli.js` in-process (no re-spawn — the parent tool owns the Node runtime) - `src/cf-wrangler/` — The `cf-wrangler` delegate entrypoint (see below) - `templates/` — Worker templates diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index fdb3013016..5a9a6d111c 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -2421,13 +2421,18 @@ export async function main(argv: string[]): Promise { } try { await handleError(e, configArgs, argv); - } catch { + } catch (handleErrorErr) { // handleError itself threw before it could log the error. // Fall back to raw stderr so the user always sees something. - const message = e instanceof Error ? e.message : String(e); - if (message) { - process.stderr.write(`\n${message}\n`); - } + const message = + e instanceof Error ? (e.stack ?? e.message) : String(e); + const handlerMessage = + handleErrorErr instanceof Error + ? (handleErrorErr.stack ?? handleErrorErr.message) + : String(handleErrorErr); + process.stderr.write( + `\n${message}${handlerMessage ? `\n\n(error handler also failed: ${handlerMessage})` : ""}\n` + ); } throw e; } From 01e9a4c3c07238c6813ce9b8583faa6a240d5120 Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Wed, 17 Jun 2026 11:04:12 +0530 Subject: [PATCH 3/5] style: fix oxfmt formatting in index.ts --- packages/wrangler/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 5a9a6d111c..f70ba66d5e 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -2424,8 +2424,7 @@ export async function main(argv: string[]): Promise { } catch (handleErrorErr) { // handleError itself threw before it could log the error. // Fall back to raw stderr so the user always sees something. - const message = - e instanceof Error ? (e.stack ?? e.message) : String(e); + const message = e instanceof Error ? (e.stack ?? e.message) : String(e); const handlerMessage = handleErrorErr instanceof Error ? (handleErrorErr.stack ?? handleErrorErr.message) From 7dc6701ed2da8c48839d6931abfbcdc8569d411b Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Fri, 19 Jun 2026 10:23:45 +0530 Subject: [PATCH 4/5] test(miniflare): harden browser rendering tests for CI Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix byte-length bug in messageToChunks: loop bound was data.length (string chars) instead of encodedUint8Array.length (encoded bytes) - Increase describe.sequential timeout 20s → 120s for Chrome download - Add per-test timeout: 120s to BROWSER_RENDERING_RETRY so retried tests don't hit a stale timeout from a previous run --- packages/miniflare/test/plugins/browser/index.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/miniflare/test/plugins/browser/index.spec.ts b/packages/miniflare/test/plugins/browser/index.spec.ts index 51583009eb..c6554c40d0 100644 --- a/packages/miniflare/test/plugins/browser/index.spec.ts +++ b/packages/miniflare/test/plugins/browser/index.spec.ts @@ -34,7 +34,7 @@ async function sendMessage(ws: WebSocket, message: unknown) { const chunks: Uint8Array[] = [firstChunk]; for ( let i = FIRST_CHUNK_DATA_SIZE; - i < data.length; + i < encodedUint8Array.length; i += MAX_MESSAGE_SIZE ) { chunks.push(encodedUint8Array.slice(i, i + MAX_MESSAGE_SIZE)); @@ -63,6 +63,7 @@ async function waitForClosedConnection(ws: WebSocket): Promise { } const BROWSER_RENDERING_RETRY = { + timeout: 120_000, retry: { condition: /Chrome readiness probe .* timed out|Test timed out/i, count: 3, @@ -83,7 +84,7 @@ export default { // We need to run browser rendering tests in a serial manner to avoid a race condition installing the browser. // We set the timeout quite high here as one of these tests will need to download the Chrome headless browser. -describe.sequential("browser rendering", { timeout: 20_000 }, () => { +describe.sequential("browser rendering", { timeout: 120_000 }, () => { // The CLI spinner outputs to stdout, so we mute it during tests beforeEach(() => { vi.spyOn(process.stdout, "write").mockImplementation(() => true); From db709ba856fb4c518d3f3bf38252ccc2a55e4f60 Mon Sep 17 00:00:00 2001 From: MatinGathani <70268627+matingathani@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:28:15 +0530 Subject: [PATCH 5/5] Update .changeset/fix-node-v26-silent-exit.md Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/fix-node-v26-silent-exit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fix-node-v26-silent-exit.md b/.changeset/fix-node-v26-silent-exit.md index 2fb4312499..28a1cb2c9e 100644 --- a/.changeset/fix-node-v26-silent-exit.md +++ b/.changeset/fix-node-v26-silent-exit.md @@ -2,7 +2,7 @@ "wrangler": patch --- -Remove deprecated `--experimental-vm-modules` flag and prevent silent exit on unexpected errors +Fix silent exit with no error message on Node.js v26 `wrangler` was silently exiting with code 1 on Node.js v26 with no error message shown. This release fixes two independent issues that caused this behaviour: