From 70d5f51d49ca18f15fb09ee955799a66077acf71 Mon Sep 17 00:00:00 2001 From: Henrik Kurelid Date: Fri, 30 Oct 2026 13:18:51 +0100 Subject: [PATCH 1/3] fix(nr-7sn1): pull requests can merge again: Playwright CI jobs run on ubuntu-24.04 ubuntu-latest moved to Ubuntu 26.04, which Playwright 1.60 refuses ("Playwright does not support chromium on ubuntu26.04-x64"), so web-container failed before testing anything. Pin web-container and web-integration to ubuntu-24.04, the image they were green on. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WCDKexBLzRiHm26TufpEph --- .github/workflows/ci.yml | 8 +++-- scripts/test_playwright_runner_image.py | 46 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 scripts/test_playwright_runner_image.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfc75a571..1e09f5d7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -403,7 +403,9 @@ jobs: # per-shard names need no fan-in job. web-integration: name: web-integration (${{ matrix.shard }}/2) - runs-on: ubuntu-latest + # Pinned, not ubuntu-latest: Playwright 1.60 refuses ubuntu26.04 (nr-7sn1). Move it together + # with a Playwright that supports the new image. + runs-on: ubuntu-24.04 # The tests take about seven minutes on a shard (nr-y7yu, one worker). The limit was raised to 30 when installing # Playwright's system packages from a slow apt mirror took up to eleven; that step no longer # touches apt unless a library is missing (nr-2e2l). @@ -488,7 +490,9 @@ jobs: retention-days: 7 web-container: - runs-on: ubuntu-latest + # Pinned, not ubuntu-latest: Playwright 1.60 refuses ubuntu26.04 (nr-7sn1). Move it together + # with a Playwright that supports the new image. + runs-on: ubuntu-24.04 timeout-minutes: 25 needs: [changes, python-tests, rust-test-build, rust-test-run, rust-lint, wasm, web, web-integration] if: >- diff --git a/scripts/test_playwright_runner_image.py b/scripts/test_playwright_runner_image.py new file mode 100644 index 000000000..68649366e --- /dev/null +++ b/scripts/test_playwright_runner_image.py @@ -0,0 +1,46 @@ +"""Every CI job that installs Playwright's Chromium runs on a pinned Ubuntu image (nr-7sn1). + +``ubuntu-latest`` moved to Ubuntu 26.04 in October 2026, and the Playwright the lockfile carries +(1.60) refuses it: ``playwright install chromium`` failed with "Playwright does not support +chromium on ubuntu26.04-x64" before any test ran, and web-container blocked every pull request. +A job that installs the browser therefore names the image it was proven on rather than following +``ubuntu-latest``; moving it is a deliberate change made together with a Playwright that supports +the new image. The workflow is read as text, as ``test_web_integration_ci.py`` does. +""" + +import re +import unittest +from pathlib import Path + +CI_YML = Path(__file__).resolve().parent.parent / ".github" / "workflows" / "ci.yml" +PINNED_IMAGE = "ubuntu-24.04" + + +def jobs() -> dict[str, str]: + """Each job of ci.yml by name, as the text of its body.""" + + ci = CI_YML.read_text(encoding="utf-8") + jobs_text = ci.split("\njobs:\n", 1)[1] + return { + match.group(1): match.group(2) + for match in re.finditer(r"^ ([\w-]+):\n((?:(?: {4}.*)?\n)+)", jobs_text, re.MULTILINE) + } + + +class PlaywrightRunnerImageTest(unittest.TestCase): + def test_jobs_installing_playwright_are_found(self) -> None: + installing = [name for name, body in jobs().items() if "playwright install" in body] + self.assertEqual(sorted(installing), ["web-container", "web-integration"]) + + def test_jobs_installing_playwright_pin_their_image(self) -> None: + for name, body in jobs().items(): + if "playwright install" not in body: + continue + with self.subTest(job=name): + runs_on = re.search(r"^ runs-on: (.+)$", body, re.MULTILINE) + assert runs_on is not None, f"{name} has no runs-on" + self.assertEqual(runs_on.group(1), PINNED_IMAGE) + + +if __name__ == "__main__": + unittest.main() From 702a9a2bce24ffc4f798ec6f4ebc246841cd9e0c Mon Sep 17 00:00:00 2001 From: Henrik Kurelid Date: Fri, 30 Oct 2026 13:29:30 +0100 Subject: [PATCH 2/3] fix(nr-7sn1): key the Playwright browser cache by the pinned runner image Review of #3396: a cached Chromium skips the install step, which is what hid the ubuntu26.04 failure in web-integration. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WCDKexBLzRiHm26TufpEph --- .github/workflows/ci.yml | 2 +- scripts/test_playwright_runner_image.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e09f5d7a..3ff79d93d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -441,7 +441,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/ms-playwright - key: playwright-chromium-${{ hashFiles('pnpm-lock.yaml') }} + key: playwright-chromium-ubuntu-24.04-${{ hashFiles('pnpm-lock.yaml') }} - name: Install Playwright Chromium if: steps.playwright-cache.outputs.cache-hit != 'true' run: | diff --git a/scripts/test_playwright_runner_image.py b/scripts/test_playwright_runner_image.py index 68649366e..5b4cb0cfc 100644 --- a/scripts/test_playwright_runner_image.py +++ b/scripts/test_playwright_runner_image.py @@ -41,6 +41,18 @@ def test_jobs_installing_playwright_pin_their_image(self) -> None: assert runs_on is not None, f"{name} has no runs-on" self.assertEqual(runs_on.group(1), PINNED_IMAGE) + def test_cached_browsers_are_keyed_by_the_pinned_image(self) -> None: + """A cached Chromium skips the install, the one step that checks the image (review of #3396). + + The cache once hid this very bug in web-integration, so moving the pin must miss the cache. + """ + + keys = [(name, key) for name, body in jobs().items() for key in re.findall(r"key: (playwright-\S+)", body)] + self.assertTrue(keys, "no Playwright browser cache found in ci.yml") + for name, key in keys: + with self.subTest(job=name): + self.assertIn(PINNED_IMAGE, key) + if __name__ == "__main__": unittest.main() From 3e613b631723854782db9ff02e3e8930df5a2eb9 Mon Sep 17 00:00:00 2001 From: Henrik Kurelid Date: Fri, 30 Oct 2026 13:47:26 +0100 Subject: [PATCH 3/3] =?UTF-8?q?docs(nr-7sn1):=20retrospective=20=E2=80=94?= =?UTF-8?q?=20ubuntu-latest=20moved=20to=2026.04=20and=20a=20cache=20hid?= =?UTF-8?q?=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WCDKexBLzRiHm26TufpEph --- docs/retrospectives/nr-7sn1.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/retrospectives/nr-7sn1.md diff --git a/docs/retrospectives/nr-7sn1.md b/docs/retrospectives/nr-7sn1.md new file mode 100644 index 000000000..ae322efa6 --- /dev/null +++ b/docs/retrospectives/nr-7sn1.md @@ -0,0 +1,11 @@ +# nr-7sn1: ubuntu-latest moved to 26.04 under us, and a cache hid it + +**What happened:** The web-container job failed on PR #3395 with "Playwright does not support chromium on ubuntu26.04-x64". It failed before any test ran, and with nothing in the repository having changed. GitHub had moved `ubuntu-latest` to Ubuntu 26.04, and our lockfile's Playwright (1.60.0) refuses that image. web-integration ran on the same image and stayed green, but only because its Chromium came from an Actions cache keyed on the lockfile alone, so its install step never ran. That was luck, not health. + +**Why:** Jobs that download a host-specific browser followed a floating runner label. A cache keyed without the image skipped the one step that checks host support. + +**Cost:** Every pull request that ran web-container was blocked (PR #3395 failed twice; nr-ri8n and nr-1wbx queued behind it). One P0 bug pass. + +**Prevent by:** `scripts/test_playwright_runner_image.py` requires every job that runs `playwright install` to name `ubuntu-24.04`, and every Playwright cache key to carry that image. Moving the image is therefore a deliberate change, made together with a Playwright upgrade (1.64.1 is the first release seen to list ubuntu26.04). + +**Seen before:** No.