Skip to content

[vitest-pool-workers] Report unavailable built-ins as module errors instead of crashing workerd - #14882

Open
petebacondarwin wants to merge 1 commit into
mainfrom
fix/vitest-pool-workers-builtin-self-redirect
Open

[vitest-pool-workers] Report unavailable built-ins as module errors instead of crashing workerd#14882
petebacondarwin wants to merge 1 commit into
mainfrom
fix/vitest-pool-workers-builtin-self-redirect

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #14590.

A Worker whose module graph statically reached a compatibility-gated built-in that wasn't enabled — for example import "node:child_process" without nodejs_compat — took down workerd with *** Received signal #11: Segmentation fault: 11 at pool startup, before any test ran. Vitest reported only Worker exited unexpectedly, naming neither the module nor the file that imported it. The import didn't even have to be called; being reachable from the entrypoint was enough. The same Worker under wrangler dev fails cleanly with No such module "node:child_process".

Root cause

The fallback service answered these specifiers with a 301 to /${specifier}, on the assumption that workerd resolves them relative to the referrer and needs redirecting to the modules root. It doesn't: workerd roots node:/cloudflare:/workerd: specifiers itself (kj::Path::parse(spec) in jsg/modules.c++) and strips the leading / before querying us. The redirect therefore pointed straight back at the specifier workerd was already resolving.

workerd's legacy module registry caches that redirect and re-enters resolve() with the identical path, with no self-redirect check and no recursion bound, so it recursed until the stack overflowed. Traced with NODE_DEBUG=vitest-pool-workers:module-fallback, the crash follows the redirect immediately, with no second fallback request:

import("node:child_process") relative to …/vitest/dist/module-evaluator.js: redirect: /node:child_process
*** Received signal #11: Segmentation fault: 11

Both guards for this already exist in the new module registry (jsg/modules-new.c++, including a self-alias check commented "a buggy fallback resolver could end up in an infinite loop of aliasing"), but were never backported to the legacy one, which is still the default.

Approach

Such a specifier only reaches the fallback service once workerd's own registry has missed, so it is by definition unavailable at this Worker's compatibility date and flags. It's now reported as not found, and workerd raises its own No such module "node:child_process" — matching wrangler dev.

The guard sits in load(), the single place a redirect is emitted, so it covers the workerdBuiltinModules branch in resolve() and both of viteResolve()'s external branches at once, and applies to require() as well as import. It needs no compatibility-flag table mirrored into the pool, and it covers gated cloudflare:* modules (cloudflare:sockets, cloudflare:email, …) as well as node:*.

The accompanying pool error now names the module and points at compatibility flags, instead of suggesting you bundle it with Vite — advice that can't help for a module built into the runtime.

Verified against the reporter's repro: Error: No such module "node:child_process". and no crash.

Notes for reviewers

  • The real fix belongs in workerd — a local dev service returning a 301 should not be able to SIGSEGV the runtime, and any other self-redirecting fallback service still can. Worth porting the modules-new.c++ guards to ModuleRegistryImpl::resolve. I've left the analysis on vitest-pool-workers: workerd segfaults (signal 11) at pool startup when the Worker's module graph has an unresolvable node:* import, instead of a clean "No such module" error #14590.
  • Separately, miniflare's waitForExit discards (code, signal), which is why the crash surfaced as Worker exited unexpectedly rather than naming SIGSEGV. Capturing the signal would make failures like this much easier to diagnose.
  • The integration test reproduces the segfault verbatim without the fix, so it is a genuine regression guard. The four new unit tests likewise all return 301 without it; the negative control (a prefixed specifier that resolves to a real file must still redirect) passes both ways.
  • packages/vitest-pool-workers has 5 pre-existing failures on main (chunking.test.ts ×1, console.test.ts ×3, filtering.test.ts ×1). I confirmed they're identical on a clean baseline and unrelated to this change.

  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this fixes internal module-resolution behaviour in vitest-pool-workers — a crash becomes a clear error. No user-facing API or documented behaviour changes.

Note

This is a contribution from an AI agent: OpenCode, claude-opus-5. Worked under direct human supervision.


Open in Devin Review

…nstead of crashing workerd

A Worker whose module graph statically reached a compatibility-gated built-in
that wasn't enabled (e.g. `import "node:child_process"` without `nodejs_compat`)
took down the runtime with `*** Received signal #11: Segmentation fault` before
any test ran, reporting only "Worker exited unexpectedly" and naming no module.

The module fallback service answered these specifiers with a redirect to the
modules root, but `workerd` already resolves `node:`/`cloudflare:`/`workerd:`
specifiers there and strips the leading `/` before querying, so the redirect
pointed back at the specifier `workerd` was resolving. Its module registry
caches that redirect and re-enters resolution with the identical path, with no
self-redirect check and no recursion bound, so it recursed until the stack
overflowed.

Such a specifier only reaches the fallback service once `workerd`'s own registry
has missed, so report it as not found instead. `workerd` then raises
`No such module "node:child_process"`, matching `wrangler dev`.

Fixes #14590
@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 89dd105

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Jul 27, 2026
@workers-devprod
workers-devprod requested review from a team and emily-shen and removed request for a team July 27, 2026 20:33
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/vitest-pool-workers-unavailable-builtin-crash.md: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/src/pool/module-fallback.ts: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/test/module-fallback.test.ts: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/test/unavailable-builtin.test.ts: [@cloudflare/wrangler]

@github-actions

Copy link
Copy Markdown
Contributor

✅ All changesets look good

@ask-bonk

ask-bonk Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

That's a noted future improvement, out of scope for this PR.

I've reviewed the code thoroughly:

  1. The core guard (load() line 562) correctly catches the self-redirect case and covers both resolve() branches (workerdBuiltinModules and viteResolve) and both import/require methods, as claimed.

  2. Correctness of the "not found" reporting: The claim that workerd only queries the fallback service after its own registry misses holds — enabled builtins never reach the fallback, so no false positives for available modules.

  3. The negative control (cloudflare:test-internal resolving to a real lib file) is preserved because it resolves to a non-/${target} path.

  4. Production RTTI concern: workerdBuiltinModules is built with nodejs_compat + today's date, so gated builtins like node:child_process are in the set and flow through resolve() line 346 → load() guard. Verified this produces the intended throw.

  5. Tests are comprehensive: unit tests for import/require/cloudflare variants, the advice message, the negative control, plus an integration test reproducing the actual segfault.

  6. Changeset is correct (patch, references the user-facing package, uses wrangler.json style implicitly, no headers).

No logic bugs, security issues, or backward-compatibility violations found. The implementation matches its documented reasoning.

LGTM

github run

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14882

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@14882

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14882

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14882

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14882

miniflare

npm i https://pkg.pr.new/miniflare@14882

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14882

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14882

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14882

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14882

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14882

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14882

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14882

wrangler

npm i https://pkg.pr.new/wrangler@14882

commit: 89dd105

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

2 participants