Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "549170099226f816a4b204ea1d8fa102fb79eefa";
export const WEBKIT_VERSION = "autobuild-preview-pr-371-4e9f0037";

Check failure on line 6 in scripts/build/deps/webkit.ts

View check run for this annotation

Claude / Claude Code Review

WEBKIT_VERSION pinned to ephemeral preview-PR tag

`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.

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.

🔴 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

  1. This PR merges to main with WEBKIT_VERSION = "autobuild-preview-pr-371-4e9f0037".
  2. 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-4e9f0037 release.
  3. A developer (or CI runner) does a fresh checkout with an empty cache and runs bun bd.
  4. prebuiltUrl() in webkit.ts computes https://github.com/oven-sh/WebKit/releases/download/autobuild-preview-pr-371-4e9f0037/bun-webkit-<os>-<arch><suffix>.tar.gz.
  5. The download returns HTTP 404. prebuiltDownloadError() throws "WebKit preview release is gone" and the build fails.
  6. Every fresh CI checkout on main is now broken until someone lands a follow-up commit changing WEBKIT_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

  • main becomes 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:

  1. Land SynchronousModuleQueue: replay diverted reactions against their own realm WebKit#371 on oven-sh/WebKit main.
  2. Set WEBKIT_VERSION to the resulting 40-hex main-branch commit SHA (the previous value 549170099226f816a4b204ea1d8fa102fb79eefa is stated to be the parent, so only Fix typo #371's change rides along).
  3. 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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).


/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
42 changes: 42 additions & 0 deletions test/js/bun/jsc/shadow.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, it } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";

it("shadow realm works", () => {
const red = new ShadowRealm();
Expand All @@ -8,3 +9,44 @@ it("shadow realm works", () => {
expect(globalThis.someValue).toBe(1);
expect(result).toBe(2);
});

// require(esm) drives the module loader through a VM-wide synchronous queue.
// Reactions from a ShadowRealm's own module loader that fire during that drain
// must be replayed against the ShadowRealm's registry, not the caller's; if
// they aren't, the first importValue registers the module in the wrong realm
// and the second importValue loads a fresh copy instead of the cached one.
it("importValue caches modules in the ShadowRealm when kicked off under require(esm)", async () => {
using dir = tempDir("shadow-realm-sync-queue", {
"counter.mjs": `let n = 0; export const getCounter = () => n++;`,
"trigger.mjs": `
import url from "node:url";
import path from "node:path";
const mod = url.pathToFileURL(path.join(import.meta.dirname, "counter.mjs")).href;
const realm = new ShadowRealm();
const first = realm.importValue(mod, "getCounter");
export { realm, first, mod };
`,
"entry.cjs": `
const { realm, first, mod } = require("./trigger.mjs");
(async () => {
const a = await first;
const b = await realm.importValue(mod, "getCounter");
const { getCounter: outer } = await import(mod);
console.log(JSON.stringify({ a0: a(), b0: b(), b1: b(), outer: outer() }));
})().catch(e => { console.error(e); process.exit(1); });
`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "entry.cjs"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
// a and b must share module state inside the ShadowRealm; the outer realm's
// import of the same specifier must be a separate instance.
expect(JSON.parse(stdout.trim())).toEqual({ a0: 0, b0: 1, b1: 2, outer: 0 });
expect(exitCode).toBe(0);
});
Loading