ShadowRealm: cache importValue module in its own realm under require(esm) - #36321
ShadowRealm: cache importValue module in its own realm under require(esm)#36321robobun wants to merge 2 commits into
Conversation
…esm) When require(esm) drives the module loader through the VM-wide synchronous queue and a ShadowRealm.importValue starts during that drain, the ShadowRealm's module-loader reactions were replayed against the caller's global. The first import was registered in the main realm's module map instead of the ShadowRealm's, so a second importValue for the same specifier fetched a fresh instance and the module leaked into the outer realm's import(). The fix is in JavaScriptCore (oven-sh/WebKit#371): store the diverting realm on SynchronousModuleTask and replay against it. This bumps WEBKIT_VERSION to that PR's preview build and adds a regression test.
|
The WebKit preview build for oven-sh/WebKit#371 ( CI build 84921 finished: 192/196 passed.
Ready for review. |
|
Updated 5:05 AM PT - Jul 29th, 2026
❌ @robobun, your commit 64028c7 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 36321That installs a local version of the PR into your bun-36321 --bun |
|
Warning Review limit reached
Next review available in: 10 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
| * From https://github.com/oven-sh/WebKit releases. | ||
| */ | ||
| export const WEBKIT_VERSION = "549170099226f816a4b204ea1d8fa102fb79eefa"; | ||
| export const WEBKIT_VERSION = "autobuild-preview-pr-371-4e9f0037"; |
There was a problem hiding this comment.
🔴 WEBKIT_VERSION is pinned to autobuild-preview-pr-371-4e9f0037, an ephemeral preview tag that GitHub deletes when oven-sh/WebKit#371 merges or closes — at which point every fresh checkout/CI build 404s on the WebKit prebuilt download. Before merge, swap this to the merged main-branch SHA of oven-sh/WebKit (once #371 lands) and verify prebuilt artifacts exist for every platform × flavor.
Extended reasoning...
What the bug is
scripts/build/deps/webkit.ts:6 sets WEBKIT_VERSION = "autobuild-preview-pr-371-4e9f0037". This is an autobuild-preview-pr-* tag — a preview release published for an unmerged WebKit PR (oven-sh/WebKit#371), not a stable main-branch commit. The PR description itself acknowledges this: "This PR bumps WEBKIT_VERSION to that PR's preview build."
Why this is merge-blocking
The repo's own review rules in .claude/docs/landing-prs.md § Dependencies & vendoring state explicitly:
Never merge a pin to an ephemeral artifact (preview tags, unmerged-PR builds) — swap to the merged upstream SHA and verify prebuilt artifacts exist for every platform × flavor before merge.
And the build system's own error handling confirms exactly what happens when this class of pin goes stale — scripts/build/download.ts:278-294:
The
autobuild-preview-pr-*WebKit tags are the sharp edge: GitHub deletes the preview release when the PR merges or closes, so every build 404s at once. … Those releases only exist while the WebKit PR is open — this one has merged, closed, or been re-tagged.
Concrete failure walkthrough
- This PR merges to
mainwithWEBKIT_VERSION = "autobuild-preview-pr-371-4e9f0037". - SynchronousModuleQueue: replay diverted reactions against their own realm WebKit#371 merges (or closes, or gets a new preview build). GitHub deletes the
autobuild-preview-pr-371-4e9f0037release. - A developer (or CI runner) does a fresh checkout with an empty cache and runs
bun bd. prebuiltUrl()inwebkit.tscomputeshttps://github.com/oven-sh/WebKit/releases/download/autobuild-preview-pr-371-4e9f0037/bun-webkit-<os>-<arch><suffix>.tar.gz.- The download returns HTTP 404.
prebuiltDownloadError()throws"WebKit preview release is gone"and the build fails. - Every fresh CI checkout on
mainis now broken until someone lands a follow-up commit changingWEBKIT_VERSION.
Nothing in the existing code prevents this — the dedicated error handler in download.ts exists precisely because this failure mode has bitten before; it makes the crash legible but does not avert it.
Impact
mainbecomes unbuildable from a clean cache the moment the upstream WebKit PR's lifecycle changes — a window that is entirely outside this repo's control.- Anyone bisecting through this commit range in the future will hit a build that can never be reproduced (the artifact is gone forever).
Fix
Before merging this PR:
- Land SynchronousModuleQueue: replay diverted reactions against their own realm WebKit#371 on oven-sh/WebKit
main. - Set
WEBKIT_VERSIONto the resulting 40-hex main-branch commit SHA (the previous value549170099226f816a4b204ea1d8fa102fb79eefais stated to be the parent, so only Fix typo #371's change rides along). - Verify prebuilt tarballs exist under
https://github.com/oven-sh/WebKit/releases/tag/autobuild-<sha>for every platform × {debug, lto, asan, musl} flavor the build matrix consumes.
There was a problem hiding this comment.
Correct, this is the staging state so CI can exercise the fix. Before merge this gets swapped to the 40-hex main SHA once oven-sh/WebKit#371 lands and its autobuild-<sha> release is published (the preview build parent is the currently pinned 549170099, so only that one commit rides along).
Problem
require(esm)drives JSC's module loader through a VM-wide synchronous queue (vm.m_synchronousModuleQueue). If aShadowRealm.importValuestarts while that queue is active (e.g. from the top level of the required ESM), the ShadowRealm's module-loader reactions are diverted to the queue and then replayed against the caller'sglobalObjectinstead of the ShadowRealm's. The module is registered in the main realm's module map, so:importValuefor the same specifier loads a fresh instance instead of the cached one, andimport()in the outer realm sees the ShadowRealm's instance.#34121 wraps every CommonJS entry load in the same queue, which makes Node's
test/parallel/test-shadow-realm-module.jsfail there.Cause
JSModuleLoader::drainSynchronousModuleQueuepasses its caller'sglobalObjecttorunInternalMicrotaskfor every queued task. The four diversion points inJSPromise.cppall have the reaction's own realm in scope (it is exactly whatqueueMicrotaskwould have used) but do not store it onSynchronousModuleTask.Fix
JavaScriptCore side: oven-sh/WebKit#371 stores the realm on
SynchronousModuleTask, replays against it in the drain loop (including the exception-pathqueueMicrotask), and marks it inVM::visitAggregateImpl.This PR bumps
WEBKIT_VERSIONto that PR's preview build (parent is the currently pinned549170099, so nothing else rides along) and adds a regression test.Verification
With the fix (local WebKit build):
Also ran
test/parallel/test-shadow-realm-module.jsand therequire-esm-*sync-queue tests; all pass.Unblocks #34121 for
test-shadow-realm-module.js.[decide:webkit] gate passed · iteration 1 · 2 files touched
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 1 rejected · iteration 1
evidence per changed file