Skip to content

fix(ci): close the two post-#370 CI races - denylist hydration vs sw/web-fetch, and the Firefox Run-control probe - #372

Open
jonybur wants to merge 2 commits into
mainfrom
fix/web-fetch-denylist-hydration-race
Open

fix(ci): close the two post-#370 CI races - denylist hydration vs sw/web-fetch, and the Firefox Run-control probe#372
jonybur wants to merge 2 commits into
mainfrom
fix/web-fetch-denylist-hydration-race

Conversation

@jonybur

@jonybur jonybur commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

What broke

Three intermittent CI reds surfaced after #370 merged, all races rather than code breaks (each passed on other runs of identical trees):

  1. packaged page boot (red on main's Add first-class Firefox Notebook modules #370 merge run). The preview remote-import probe failed with The sensitive-origin policy is unavailable after 0 fixture requests. The denylist seed hydrates async at SW boot and webFetch's getDenylist does a synchronous readiness check, so a sw/web-fetch request (Notebook module fetch, VM egress) racing a cold start was refused instead of waiting. Every tool-dispatch path already awaits denylistReady in buildToolContext; this engine route was the one fetch boundary that did not.
  2. Firefox suite: the Run-control probe read notebook.js the instant the button flipped to Stop, but the flip happens at run-reserve time, before js/eval's awaited mirror of the agent code lands in OPFS - the single-shot read saw the previous run's code. The product ordering is correct (reserve first prevents double-runs; the mirror completes before the run starts); the test's read was the bug.
  3. Firefox suite: the remote-fetch stop probe slept a fixed 150ms then aborted - on a slow runner the abort landed before the resolver had issued the slow fetch, so the fixture saw 0 requests instead of the in-flight cancel the test pins.

What changed

  • sw/web-fetch awaits an injected awaitDenylistPolicy gate (requireDenylistPolicy(await denylistReady), defined next to denylistReady, wired shorthand) before any fetch work. A racing request waits out the one-time load; a genuinely failed hydration still refuses before any egress - fail-closed unchanged, and the sync check in getDenylist stays as the last-resort chokepoint for other direct callers.
  • The Run-control probe polls the notebook.js read (bounded) until the mirrored content appears.
  • The stop probe aborts only once the fixture's new status endpoint confirms the slow request is on the wire (read through the same audited sw/web-fetch relay the resolver uses; single injected block because geckodriver gives each script a fresh sandbox). elapsedMs now measures abort to settled - the prompt-stop property - instead of including run setup.

Residual (flagged, not changed)

The neighboring timedOutFetch probe has a smaller latent race of the same family: its 400ms eval deadline must fire after run setup has issued the slow fetch. It has never failed in CI and fixing it deterministically means changing its contract (the deadline is intrinsic to the eval), so it is left for a deliberate follow-up if it ever fires. The dweb two-peer red on this PR's second run is unrelated: the only diff from its green first run is the Firefox harness file, and the lane passed on the ten runs before - an infra blip, re-exercised by this push.

Verified

  • Two new bun tests pin the route gate (racing fetch waits, order-asserted; failed hydration refuses before vmHttpFetch), revert-proven red without the fix.
  • Full local gate set green: bun suite (5665), typecheck, lint, boundary, imports, tscheck, gen drift, copy + source hygiene, invariants, in-browser (864), check:pages both channels, and the full Firefox runtime suite (all checks + 858 Gecko browser tests) against pinned Firefox 153 + geckodriver 0.37.1, green including all three previously-racing probes.
  • packaged page boot already ran green twice on this PR with fix 1 in place.

@jonybur
jonybur requested a review from NotASithLord as a code owner August 9, 2026 12:30
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Visual regression

No visual drift. Every state renders identically to the committed baselines.

📸 Browse the visual gallery — 54 baseline screens (light + dark) as of this commit.

jonybur added a commit that referenced this pull request Aug 9, 2026
… flush

The Run-control probe read notebook.js the instant the button flipped to
Stop. The flip happens at run-reserve time, BEFORE js/eval's awaited
mirror of the agent code into notebook.js lands in OPFS, so on a slow
runner the single-shot read saw the previous run's code and failed the
suite (branch runs of #372 and #360 hit this; the product ordering is
correct - reserve first prevents double-runs, and the mirror completes
before the run starts). The read now polls until the mirrored content
appears, bounded at 50 attempts, so a slow flush is a wait instead of a
false mismatch.

Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
@jonybur jonybur changed the title fix(egress): let sw/web-fetch await denylist seed hydration on a cold SW start fix(ci): close the two post-#370 CI races - denylist hydration vs sw/web-fetch, and the Firefox Run-control probe Aug 9, 2026
jonybur added a commit that referenced this pull request Aug 9, 2026
…st arrival

The remote-fetch stop probe slept a fixed 150ms after starting the run,
then aborted - on a slow runner the abort landed before the resolver had
even issued the slow fetch, so the fixture saw 0 requests instead of the
in-flight cancel the test pins (CI hit this on the #372 branch run).

The fixture now serves a status endpoint reporting how many slow-module
requests have arrived, and the probe polls it through the same audited
sw/web-fetch relay the resolver uses, aborting only once the request is
on the wire. Single injected block (geckodriver gives each script a
fresh sandbox, so cross-call state is not an option), and elapsedMs now
measures abort to settled - the prompt-stop property - instead of
including run setup.

Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
jonybur added a commit that referenced this pull request Aug 9, 2026
…st arrival

The remote-fetch stop probe slept a fixed 150ms after starting the run,
then aborted - on a slow runner the abort landed before the resolver had
even issued the slow fetch, so the fixture saw 0 requests instead of the
in-flight cancel the test pins (CI hit this on the #372 branch run).

The fixture now serves a status endpoint reporting how many slow-module
requests have arrived, and the probe polls it through the same audited
sw/web-fetch relay the resolver uses, aborting only once the request is
on the wire. Single injected block (geckodriver gives each script a
fresh sandbox, so cross-call state is not an option), and elapsedMs now
measures abort to settled - the prompt-stop property - instead of
including run setup.

Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
@jonybur
jonybur force-pushed the fix/web-fetch-denylist-hydration-race branch from fcce931 to 53fc0ec Compare August 9, 2026 13:17
jonybur added 2 commits August 9, 2026 19:01
… SW start

The denylist seed hydrates async at service-worker boot, and webFetch's
getDenylist does a synchronous readiness check. A sw/web-fetch request
(Notebook module fetch, VM egress) that raced a cold start was refused
with DenylistPolicyUnavailableError instead of waiting - the packaged
page boot job hit exactly this on main's #370 merge run (the same tree
passed on the PR branch and locally).

The engine route now awaits an injected awaitDenylistPolicy gate
(requireDenylistPolicy(await denylistReady)) before any fetch work, so
the race becomes a short wait while a genuinely failed hydration still
refuses before any egress. The sync check inside getDenylist stays as
the last-resort chokepoint for every other direct caller.

Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
…st arrival

The remote-fetch stop probe slept a fixed 150ms after starting the run,
then aborted - on a slow runner the abort landed before the resolver had
even issued the slow fetch, so the fixture saw 0 requests instead of the
in-flight cancel the test pins (CI hit this on the #372 branch run).

The fixture now serves a status endpoint reporting how many slow-module
requests have arrived, and the probe polls it through the same audited
sw/web-fetch relay the resolver uses, aborting only once the request is
on the wire. Single injected block (geckodriver gives each script a
fresh sandbox, so cross-call state is not an option), and elapsedMs now
measures abort to settled - the prompt-stop property - instead of
including run setup.

Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
@jonybur
jonybur force-pushed the fix/web-fetch-denylist-hydration-race branch from 53fc0ec to bef5589 Compare August 9, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant