Make WordPress Core

Opened 2 days ago

Last modified 3 hours ago

#65721 reopened enhancement

Download the Gutenberg build once per run

Reported by: lancewillett Owned by: lancewillett
Priority: normal Milestone: 7.0.3
Component: Build/Test Tools Version:
Severity: normal Keywords: has-patch has-unit-tests fixed-major
Cc: Focuses:

Description (last modified by lancewillett)

Each matrix job (roughly 177 per run) independently streams the same ~36 MB Gutenberg archive from the GitHub Container Registry, about 6.4 GB of registry traffic per run. Any one flaky stream ("Premature close") can redden the run.

Proposed change: a prepare-gutenberg reusable workflow fetches and verifies the build once (SHA-256 and size), then uploads it as a run artifact. Every PHPUnit caller consumes the shared artifact and never contacts the registry. The prep job runs only when a consumer runs.

Coverage is unchanged: the same PHPUnit jobs run with the same configurations.

Follow up to the Gutenberg artifact-fetching work in #64393.

Change History (20)

This ticket was mentioned in PR #12701 on WordPress/wordpress-develop by @lancewillett.


2 days ago
#1

## Summary

Fetch and verify the Gutenberg build once per workflow run instead of once per matrix job.

  • Add .github/workflows/reusable-prepare-gutenberg.yml, which resolves the build metadata, downloads the archive, verifies it (SHA-256 and size), and uploads it as a run artifact.
  • phpunit-tests.yml, reusable-phpunit-tests-v3.yml, and test-coverage.yml consume that shared artifact and no longer contact the registry.
  • The prep job runs only when a consumer job runs, so runs that skip PHPUnit do not pay for it.

Test coverage is unchanged: the same PHPUnit jobs run with the same configurations.

## Why

Every matrix job independently streams the same ~36 MB archive from the GitHub Container Registry. Across a full run that is roughly 6.4 GB of registry traffic for one build, and any single flaky stream can redden the whole run. Downloading once shrinks the exposure from every job to one. It also moves verification to a single place, so a bad archive fails fast and loudly instead of failing differently in each consumer.

Follow up to the Gutenberg artifact-fetching work in #64393.

This is the first of two changes on this ticket. The second caches the verified archive across runs.

## Testing

  • actionlint passes on all four workflow files.
  • The prep job's verification is the same SHA-256 and size check the downloader already performed per job, so a mismatch still fails closed.

This ticket was mentioned in PR #12702 on WordPress/wordpress-develop by @lancewillett.


2 days ago
#2

Second of two changes on this ticket, and it builds on #12701. Because a cross-repo pull request can only target a branch that exists here, this one is opened against trunk, so its diff carries the download-once commit as well. Review only the second commit, "Cache the verified Gutenberg build across runs." Once #12701 lands, this branch reduces to that single commit.

## Summary

Cache the verified Gutenberg archive across runs.

  • Key the cache on the immutable source SHA plus the blob SHA-256.
  • Re-verify (SHA-256 and size) on every restore and fail closed on a mismatch.
  • Resolve fresh metadata before every restore, so the cache can never select which build version is used.
  • Write the cache only from protected-branch pushes; pull requests and forks restore only.

## Why

Downloading once per run cut the registry fetches from every matrix job to one. Caching removes even that fetch on a warm hit, which keeps a flaky external registry off the hot path entirely rather than just narrowing the window.

Two safety properties are worth calling out. The cache is keyed on content, not on a branch or a date, so a restore either matches the exact archive the metadata asked for or is rejected. And because metadata resolves first, a stale entry can never pin CI to an old Gutenberg build. Restricting writes to protected-branch pushes keeps untrusted pull requests out of a cache that trusted runs read.

Follow up to the Gutenberg artifact-fetching work in #64393 on Trac.

## Testing

  • actionlint passes on the reusable workflow.
  • Unit tests cover cache-hit verification, mismatch rejection, and the metadata-before-restore ordering. Run with npm run test:tools.

## Note on overlap with #12700

That pull request and this one both add tools/gutenberg/tests/download.test.js from scratch and both add a test:tools script to package.json, so they conflict with each other. Suggested order is to land #12700 first; this branch then rebases so its cache tests extend that file rather than replace it. Happy to do that rebase whenever it suits the review order.

@lancewillett commented on PR #12702:


2 days ago
#3

Closing to keep the flake fix minimal. #12701 (download once per run) removes the download flake by cutting ~177 GHCR fetches per run to one. Cross-run caching is a separate speed optimization, not needed for the flake, so deferring it as a possible small follow-up on top of #12701 rather than a parallel change. If revived, two things to fix from this branch: the prepare workflow verified and extracted the archive twice on a cold run, and the archive-path guard defended against input that cannot occur.

#4 @lancewillett
2 days ago

#65720 was marked as a duplicate.

#5 @lancewillett
2 days ago

  • Description modified (diff)
  • Summary Download the Gutenberg build once per run and cache it across runsDownload the Gutenberg build once per run

Reduced scope to download-once. The original second stage (cross-run caching) is deferred. Download-once alone removes the flaky per-job downloads by cutting the ~177 fetches per run to one, and caching is a separate speed optimization that can follow up on its own if wanted.

@lucasbustamante commented on PR #12701:


35 hours ago
#6

When things are calmer it might be worth backporting this fix to older branches as well to make CI more reliable when testing backported branches.

@lancewillett commented on PR #12701:


31 hours ago
#7

## Note on the download-once tradeoff

This PR moves the Gutenberg build download from once-per-matrix-job to once-per-run: a single prepare-gutenberg job the matrix depends on via needs:. That is a deliberate tradeoff worth an explicit call.

Why do it. Today each job resolves the mutable tag→SHA independently. If the tag moves mid-run, different jobs can silently test against different Gutenberg builds. Resolving once and sharing the SHA plus artifact closes that skew.

The cost. A failure in the single gate skips the whole matrix via needs:, where today a transient fetch failure fails only one job. CI is re-runnable, so the impact is a re-run, not lost work, but the blast radius is real.

How we bounded it. The download path now has bounded retry and timeout on the blob, a fresh-token refetch on a mid-retry 401, and the same retry and timeout on the token and manifest fetches that precede it. The whole single-gate path is hardened, not just the blob.

Why no per-job fallback. A fallback to the old per-job download would re-introduce the very skew this fixes (a fallen-back job could grab a different build) and keep two download paths to maintain.

Flagging this so it is a deliberate yes, not a nod-through. Happy to revisit if you would prefer the fallback.

@lancewillett commented on PR #12701:


30 hours ago
#8

I think this will break on branches 5.9–7.0 because the two required inputs are not passed

Great catch, @adimoldovan . Confirmed and fixed in eeee175aca

#9 @lancewillett
28 hours ago

  • Owner set to lancewillett
  • Resolutionfixed
  • Status newclosed

In 62859:

Build/Test Tools: Fetch and verify the Gutenberg build once per run.

Each matrix job independently streams the same ~36 MB Gutenberg archive from the GitHub Container Registry, so any one flaky stream can redden a run. Add a reusable prepare-gutenberg workflow that fetches and verifies the build once (SHA-256 and size) and uploads it as a run artifact; the PHPUnit callers consume the shared artifact and no longer contact the registry. The prep job runs only when a consumer runs, so runs that skip PHPUnit do not pay for it. Test coverage is unchanged.

Developed in: https://github.com/WordPress/wordpress-develop/pull/12701

Props adrianmoldovanwp, lucasbustamante
Fixes #65721

This ticket was mentioned in PR #12724 on WordPress/wordpress-develop by @lancewillett.


27 hours ago
#10

The download-once change ([62859]) added the callers of reusable-prepare-gutenberg.yml (phpunit-tests.yml, reusable-phpunit-tests-v3.yml, and test-coverage.yml) but the new reusable-prepare-gutenberg.yml file itself was not committed to trunk. Those workflows now reference a reusable workflow that does not exist, so:

  • Lint GitHub Actions workflow files fails on trunk (actionlint: could not read reusable workflow file ... no such file or directory).
  • The PHPUnit Tests workflow cannot load on trunk, because phpunit-tests.yml calls the missing prepare-gutenberg job that the whole matrix depends on.

This adds the file. It resolves the Gutenberg build once per run, verifies it (SHA-256 and size), uploads it as a gutenberg-build run artifact, and exposes the resolved gutenberg-sha output, the interface the three callers already expect.

Verified with actionlint locally: the dangling reference resolves and the new file is clean.

Follow-up to [62859]. See #65721.

#11 @lancewillett
26 hours ago

In 62862:

Build/Test Tools: Add the missing reusable-prepare-gutenberg.yml workflow.

[62859] added the callers of this reusable workflow (phpunit-tests.yml, reusable-phpunit-tests-v3.yml, and test-coverage.yml) but not the reusable-prepare-gutenberg.yml file they depend on, so those workflows reference a reusable workflow that does not exist and fail to load. Add the file: it resolves the Gutenberg build once per run, verifies it (SHA-256 and size), and uploads it as a run artifact the PHPUnit callers consume.

Developed in: https://github.com/WordPress/wordpress-develop/pull/12724

Follow-up to [62859]
See #65721

#12 @lancewillett
25 hours ago

  • Milestone Awaiting Review7.1

#13 @jorbin
10 hours ago

@lancewillett what would you think of backporting (in part) [62859][62862] to 7.0? We should be able to reuse the trunk version of reusable-prepare-gutenberg.yml so that there is only one version to maintain.

#14 @lancewillett
10 hours ago

  • Resolution fixed
  • Status closedreopened

@jorbin Good call. Since 7.0 already calls reusable-phpunit-tests-v3.yml@trunk we just add a prepare-gutenberg job to 7.0's caller that references reusable-prepare-gutenberg.yml@trunk and pass its artifact/SHA into the existing calls, so neither reusable file needs a 7.0 copy.'

We'll work on a patch.

#15 @jorbin
10 hours ago

  • Keywords fixed-major added
  • Milestone 7.17.0.3

Fantastic! Marking as fixed-major and putting in the 7.0.3 milestone to signify the intent to backport the fix.

#16 @lancewillett
8 hours ago

In 62873:

Build/Test Tools: Prepare Gutenberg once in the 7.0 PHPUnit workflow.

Merges [62859] and [62862] to the 7.0 branch.

Developed in: https://github.com/WordPress/wordpress-develop/pull/12739

Props adrianmoldovanwp, lucasbustamante.
See #64393, #65721.

#17 @lancewillett
6 hours ago

  • Resolutionfixed
  • Status reopenedclosed

Backported to 7.0 in r62873; the 7.0 PHPUnit matrix is green with the build downloaded once per run. Closing as fixed for 7.0.3.

This ticket was mentioned in PR #12743 on WordPress/wordpress-develop by @lancewillett.


5 hours ago
#18

This updates the 7.0 PHPUnit workflow to reference reusable-prepare-gutenberg.yml@trunk instead of a pinned commit SHA.

The reusable-phpunit-tests-v3.yml callers already use @trunk. Keeping both shared workflows on the same ref prevents their output contract from drifting and preserves the one-version-to-maintain intent.

No PHP matrix or caller wiring changed.

## Testing

  • actionlint .github/workflows/phpunit-tests.yml
  • git diff --check

## Use of AI Tools

AI assistance: Yes
Tool(s): Codex
Model(s): GPT-5
Used for: Applied and validated the one-line workflow reference change. The final diff was reviewed by me.

#19 @lancewillett
4 hours ago

  • Resolution fixed
  • Status closedreopened
Note: See TracTickets for help on using tickets.