diff --git a/.changeset/fix-node-v26-silent-exit.md b/.changeset/fix-node-v26-silent-exit.md new file mode 100644 index 0000000000..28a1cb2c9e --- /dev/null +++ b/.changeset/fix-node-v26-silent-exit.md @@ -0,0 +1,11 @@ +--- +"wrangler": patch +--- + +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: + +1. A stale Node.js flag that caused unexpected behaviour on Node.js v26 has been removed. + +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/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); 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/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..f70ba66d5e 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -2419,7 +2419,20 @@ 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 (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 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; } } finally {