Skip to content

Updated CI to mirror playwright image to GHCR#27318

Open
cmraible wants to merge 6 commits intomainfrom
chris-plan-playwright-ghcr-mirror
Open

Updated CI to mirror playwright image to GHCR#27318
cmraible wants to merge 6 commits intomainfrom
chris-plan-playwright-ghcr-mirror

Conversation

@cmraible
Copy link
Copy Markdown
Collaborator

@cmraible cmraible commented Apr 9, 2026

Summary

This updates E2E CI to mirror the pinned Playwright container image into GHCR and pull that mirrored image during test runs.

The change was motivated by intermittent CI failures when pulling directly from Microsoft Container Registry:

Error response from daemon: pull access denied for mcr.microsoft.com/playwright, repository does not exist or may require 'docker login': denied: ... <title>Microsoft</title> ... The request is blocked.

To reduce that flakiness, CI now:

  • computes the pinned Playwright version in job_setup
  • conditionally runs a dedicated mirror job only when the Playwright version changes
  • mirrors mcr.microsoft.com/playwright:v<version>-noble to ghcr.io/tryghost/playwright:v<version>-noble
  • cleans up older mirrored Playwright versions in GHCR with package-specific retention rules

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Walkthrough

The changes introduce container image mirroring functionality for Playwright. A new composite GitHub Action is created to facilitate pulling, tagging, and pushing container images. The CI workflow is enhanced to detect the Playwright version from package.json, check for image existence in GHCR, and conditionally mirror it from Microsoft Container Registry when needed. The cleanup workflow is updated to manage the Playwright package in GHCR with version-based retention logic sorted by update timestamps. The e2e test script is modified to default to the GHCR-hosted Playwright image instead of the upstream source. This establishes automated synchronization and lifecycle management of the Playwright container image within the project's registry.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: mirroring the Playwright container image to GHCR, which is the central objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation and detailing how the CI now mirrors the Playwright image from MCR to GHCR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chris-plan-playwright-ghcr-mirror

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.47%. Comparing base (e4e7bda) to head (77a289f).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27318      +/-   ##
==========================================
- Coverage   73.49%   73.47%   -0.02%     
==========================================
  Files        1545     1545              
  Lines      123720   123724       +4     
  Branches    14973    14971       -2     
==========================================
- Hits        90922    90910      -12     
- Misses      31777    31813      +36     
+ Partials     1021     1001      -20     
Flag Coverage Δ
admin-tests 54.37% <ø> (-0.03%) ⬇️
e2e-tests 73.47% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

cmraible added 3 commits April 9, 2026 18:43
Documented the proposed GHCR mirroring approach for Playwright so the CI changes can be reviewed and implemented from a single spec.
@cmraible cmraible force-pushed the chris-plan-playwright-ghcr-mirror branch from 349ce33 to 467e08c Compare April 10, 2026 01:43
@cmraible cmraible marked this pull request as ready for review April 10, 2026 03:40
@cmraible cmraible requested a review from 9larsons as a code owner April 10, 2026 03:40
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/mirror-container-image/action.yml:
- Around line 22-29: The current sequence uses docker pull -> docker tag ->
docker push which only mirrors a single platform variant; replace that block so
the action uses a manifest-aware copy tool (e.g., docker buildx imagetools
create or skopeo copy --all) to copy "$SOURCE_IMAGE" to "$TARGET_IMAGE" and
preserve the multi-arch manifest list; ensure the step performs any required
registry authentication beforehand and uses the existing SOURCE_IMAGE and
TARGET_IMAGE variables when invoking imagetools or skopeo so the upstream
manifest list is retained.

In @.github/workflows/ci.yml:
- Around line 244-275: The job job_mirror_playwright_image must always run as a
prerequisite for Playwright consumers; move its conditional logic out of the
job-level if (so the job never gets skipped) and instead apply the mirror-only
gating at the step level (the "Login to GHCR", "Mirror Playwright image", and
"Fail when Playwright image cannot be mirrored" steps) or create a separate
always-running gate job that performs the mirror-check/mirror action, then add
job_mirror_playwright_image (or the new gate job) to the needs chain of the
first Playwright-consuming job and job_required_tests so downstream jobs cannot
start until the mirror job completes.
- Around line 205-214: Change the logic that sets image-exists so only a 200 =>
image-exists=true and a 404 => image-exists=false; treat any other HTTP_CODE
(401/429/5xx/empty TOKEN) as a fatal error to fail the workflow instead of
proceeding to mirror. Specifically, after fetching TOKEN and computing HTTP_CODE
for the manifest URL (variables TOKEN, HTTP_CODE, PLAYWRIGHT_VERSION), if TOKEN
is empty or HTTP_CODE is not 200/404 print an informative error to stderr and
exit 1; if HTTP_CODE is 200 echo "image-exists=true" >> "$GITHUB_OUTPUT"; if 404
echo "image-exists=false" >> "$GITHUB_OUTPUT".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c37f7515-84e5-4eda-8411-004bb5fa4893

📥 Commits

Reviewing files that changed from the base of the PR and between 71d7802 and 77a289f.

📒 Files selected for processing (4)
  • .github/actions/mirror-container-image/action.yml
  • .github/workflows/ci.yml
  • .github/workflows/cleanup-ghcr.yml
  • e2e/scripts/load-playwright-container-env.sh

Comment on lines +22 to +29
echo "Pulling source image: $SOURCE_IMAGE"
docker pull "$SOURCE_IMAGE"

echo "Tagging image as: $TARGET_IMAGE"
docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"

echo "Pushing mirrored image: $TARGET_IMAGE"
docker push "$TARGET_IMAGE"
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does docker pull IMAGE && docker tag IMAGE TARGET && docker push TARGET preserve a multi-platform manifest list, or only the pulled platform? What is the recommended way to copy a multi-arch image between registries?

💡 Result:

docker pull IMAGE && docker tag IMAGE TARGET && docker push TARGET only preserves (and pushes) the single platform variant that got pulled, not the original multi-platform manifest list/image index. On docker pull, the registry may return a manifest list, but Docker then selects just the matching platform and pulls that image; the engine doesn’t use a locally-stored manifest list for subsequent operations like push. [1], [2]

Recommended ways to copy a multi-arch image between registries (without rebuilding):

  • Docker-native (recommended): copy the manifest list/index directly

    docker buildx imagetools create --tag DEST_IMAGE:TAG SRC_IMAGE:TAG

    If the source is a manifest list / OCI index, imagetools create makes a “carbon copy” to the destination tag. [3]

  • Skopeo (common alternative):

    skopeo copy --all docker://SRC_IMAGE:TAG docker://DEST_IMAGE:TAG

    --all ensures the manifest list and all referenced arch images are copied. [4]

Sources:
[1] Docker Docs – Multi-platform (pull selects correct variant) (docs.docker.com)
[2] Docker Docs – docker manifest (local manifest lists not used by engine on pull) (docs.docker.com)
[3] Docker Docs – docker buildx imagetools create (“carbon copy” of manifest list/index) (docs.docker.com)
[4] Google Cloud Blog – Skopeo copy --all copies entire manifest list and associated images (cloud.google.com)

Citations:


Preserve the upstream manifest list when mirroring multi-platform images.

Lines 22-29 use docker pull, docker tag, and docker push, which only republishes the single platform variant that the runner pulled locally. For multi-arch Playwright tags, this produces a single-platform copy instead of a true multi-platform mirror. The registry returns a manifest list, but Docker selects only the matching platform during pull; subsequent tag and push operations don't preserve the original manifest structure.

Use a manifest-aware copy tool instead: docker buildx imagetools create, skopeo copy --all, or equivalent.

🔁 Suggested fix
-        echo "Pulling source image: $SOURCE_IMAGE"
-        docker pull "$SOURCE_IMAGE"
-
-        echo "Tagging image as: $TARGET_IMAGE"
-        docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"
-
-        echo "Pushing mirrored image: $TARGET_IMAGE"
-        docker push "$TARGET_IMAGE"
+        echo "Mirroring image as: $TARGET_IMAGE"
+        docker buildx imagetools create \
+          --tag "$TARGET_IMAGE" \
+          "$SOURCE_IMAGE"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "Pulling source image: $SOURCE_IMAGE"
docker pull "$SOURCE_IMAGE"
echo "Tagging image as: $TARGET_IMAGE"
docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"
echo "Pushing mirrored image: $TARGET_IMAGE"
docker push "$TARGET_IMAGE"
echo "Mirroring image as: $TARGET_IMAGE"
docker buildx imagetools create \
--tag "$TARGET_IMAGE" \
"$SOURCE_IMAGE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/mirror-container-image/action.yml around lines 22 - 29, The
current sequence uses docker pull -> docker tag -> docker push which only
mirrors a single platform variant; replace that block so the action uses a
manifest-aware copy tool (e.g., docker buildx imagetools create or skopeo copy
--all) to copy "$SOURCE_IMAGE" to "$TARGET_IMAGE" and preserve the multi-arch
manifest list; ensure the step performs any required registry authentication
beforehand and uses the existing SOURCE_IMAGE and TARGET_IMAGE variables when
invoking imagetools or skopeo so the upstream manifest list is retained.

Comment on lines +205 to +214
# Check if the image already exists in GHCR (anonymous OCI registry check)
TOKEN="$(curl -sf "https://ghcr.io/token?service=ghcr.io&scope=repository:tryghost/playwright:pull" | jq -r .token)" || TOKEN=""
HTTP_CODE="$(curl -so /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" \
"https://ghcr.io/v2/tryghost/playwright/manifests/v${PLAYWRIGHT_VERSION}-noble")"

if [ "$HTTP_CODE" = "200" ]; then
echo "image-exists=true" >> "$GITHUB_OUTPUT"
else
echo "image-exists=false" >> "$GITHUB_OUTPUT"
fi
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For GitHub Container Registry's /v2//manifests/ endpoint, which HTTP status codes indicate “manifest not found” versus auth, rate-limit, or server errors?

💡 Result:

For GET/HEAD /v2/<name>/manifests/<reference> (as implemented by GHCR via the OCI/Docker Distribution APIs):

  • Manifest not found: 404 Not Found (manifest absent/unknown). [1]
  • Auth problems:
    • 401 Unauthorized (missing/invalid auth; check WWW-Authenticate). [2]
    • 403 Forbidden (authenticated but not allowed / insufficient scope). [2]
  • Rate limit / throttling: 429 Too Many Requests. [2]
  • Server-side errors: 5xx (notably 502, 503, 504 are treated as temporary/retryable per the spec; other 5xx are server failures). [2]

Sources: [1] OCI Distribution Spec (Pulling manifests) (oci-playground.github.io), [2] Docker Registry HTTP API V2 spec (auth, 403/429 errors, 5xx guidance) (matsuand.github.io)

Citations:


Distinguish manifest not found (404) from transient errors (401/429/5xx).

Lines 205–214 currently collapse every non-200 HTTP status into image-exists=false. A transient 401, 429, or 5xx error—or even a token lookup failure—will incorrectly trigger an unnecessary mirror attempt and hard-fail fork PRs even if the image already exists. Only 404 indicates the manifest is actually missing; other errors should cause the workflow to fail rather than proceed with mirroring.

🛡️ Suggested fix
-          if [ "$HTTP_CODE" = "200" ]; then
-            echo "image-exists=true" >> "$GITHUB_OUTPUT"
-          else
-            echo "image-exists=false" >> "$GITHUB_OUTPUT"
-          fi
+          case "$HTTP_CODE" in
+            200)
+              echo "image-exists=true" >> "$GITHUB_OUTPUT"
+              ;;
+            404)
+              echo "image-exists=false" >> "$GITHUB_OUTPUT"
+              ;;
+            *)
+              echo "::error::Unexpected GHCR manifest response: $HTTP_CODE"
+              exit 1
+              ;;
+          esac
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check if the image already exists in GHCR (anonymous OCI registry check)
TOKEN="$(curl -sf "https://ghcr.io/token?service=ghcr.io&scope=repository:tryghost/playwright:pull" | jq -r .token)" || TOKEN=""
HTTP_CODE="$(curl -so /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" \
"https://ghcr.io/v2/tryghost/playwright/manifests/v${PLAYWRIGHT_VERSION}-noble")"
if [ "$HTTP_CODE" = "200" ]; then
echo "image-exists=true" >> "$GITHUB_OUTPUT"
else
echo "image-exists=false" >> "$GITHUB_OUTPUT"
fi
# Check if the image already exists in GHCR (anonymous OCI registry check)
TOKEN="$(curl -sf "https://ghcr.io/token?service=ghcr.io&scope=repository:tryghost/playwright:pull" | jq -r .token)" || TOKEN=""
HTTP_CODE="$(curl -so /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" \
"https://ghcr.io/v2/tryghost/playwright/manifests/v${PLAYWRIGHT_VERSION}-noble")"
case "$HTTP_CODE" in
200)
echo "image-exists=true" >> "$GITHUB_OUTPUT"
;;
404)
echo "image-exists=false" >> "$GITHUB_OUTPUT"
;;
*)
echo "::error::Unexpected GHCR manifest response: $HTTP_CODE"
exit 1
;;
esac
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 205 - 214, Change the logic that sets
image-exists so only a 200 => image-exists=true and a 404 => image-exists=false;
treat any other HTTP_CODE (401/429/5xx/empty TOKEN) as a fatal error to fail the
workflow instead of proceeding to mirror. Specifically, after fetching TOKEN and
computing HTTP_CODE for the manifest URL (variables TOKEN, HTTP_CODE,
PLAYWRIGHT_VERSION), if TOKEN is empty or HTTP_CODE is not 200/404 print an
informative error to stderr and exit 1; if HTTP_CODE is 200 echo
"image-exists=true" >> "$GITHUB_OUTPUT"; if 404 echo "image-exists=false" >>
"$GITHUB_OUTPUT".

Comment on lines +244 to +275
job_mirror_playwright_image:
name: Mirror Playwright image
runs-on: ubuntu-latest
needs: [job_setup]
if: needs.job_setup.outputs.is_tag != 'true' && needs.job_setup.outputs.playwright_image_exists != 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Login to GHCR
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Mirror Playwright image
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/mirror-container-image
with:
source-image: mcr.microsoft.com/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble
target-image: ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble

- name: Fail when Playwright image cannot be mirrored
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Playwright v${{ needs.job_setup.outputs.playwright_version }}-noble is not mirrored in GHCR yet. Publish ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble before running E2E for this branch."
exit 1
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.

⚠️ Potential issue | 🟠 Major

Make this an always-running prerequisite for Playwright consumers.

Nothing in the workflow currently needs this job, so on the first Playwright bump the E2E path can start pulling ghcr.io/tryghost/playwright:v…-noble before the mirror push finishes. That reintroduces the same race this PR is trying to eliminate. Also, adding this job to needs as-is is not enough, because its job-level if will skip dependents when the image already exists. The mirror condition needs to move to step level, or into an always-running gate job, before downstream jobs depend on it.

🔁 Suggested shape
  job_mirror_playwright_image:
    name: Mirror Playwright image
    runs-on: ubuntu-latest
    needs: [job_setup]
-   if: needs.job_setup.outputs.is_tag != 'true' && needs.job_setup.outputs.playwright_image_exists != 'true'
+   if: needs.job_setup.outputs.is_tag != 'true'
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Login to GHCR
-       if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
+       if: needs.job_setup.outputs.playwright_image_exists != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4

      - name: Mirror Playwright image
-       if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
+       if: needs.job_setup.outputs.playwright_image_exists != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
        uses: ./.github/actions/mirror-container-image

Then add job_mirror_playwright_image to the first Playwright-consuming job's needs chain and to job_required_tests.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
job_mirror_playwright_image:
name: Mirror Playwright image
runs-on: ubuntu-latest
needs: [job_setup]
if: needs.job_setup.outputs.is_tag != 'true' && needs.job_setup.outputs.playwright_image_exists != 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to GHCR
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Mirror Playwright image
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/mirror-container-image
with:
source-image: mcr.microsoft.com/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble
target-image: ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble
- name: Fail when Playwright image cannot be mirrored
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Playwright v${{ needs.job_setup.outputs.playwright_version }}-noble is not mirrored in GHCR yet. Publish ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble before running E2E for this branch."
exit 1
job_mirror_playwright_image:
name: Mirror Playwright image
runs-on: ubuntu-latest
needs: [job_setup]
if: needs.job_setup.outputs.is_tag != 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to GHCR
if: needs.job_setup.outputs.playwright_image_exists != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Mirror Playwright image
if: needs.job_setup.outputs.playwright_image_exists != 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: ./.github/actions/mirror-container-image
with:
source-image: mcr.microsoft.com/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble
target-image: ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble
- name: Fail when Playwright image cannot be mirrored
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Playwright v${{ needs.job_setup.outputs.playwright_version }}-noble is not mirrored in GHCR yet. Publish ghcr.io/tryghost/playwright:v${{ needs.job_setup.outputs.playwright_version }}-noble before running E2E for this branch."
exit 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 244 - 275, The job
job_mirror_playwright_image must always run as a prerequisite for Playwright
consumers; move its conditional logic out of the job-level if (so the job never
gets skipped) and instead apply the mirror-only gating at the step level (the
"Login to GHCR", "Mirror Playwright image", and "Fail when Playwright image
cannot be mirrored" steps) or create a separate always-running gate job that
performs the mirror-check/mirror action, then add job_mirror_playwright_image
(or the new gate job) to the needs chain of the first Playwright-consuming job
and job_required_tests so downstream jobs cannot start until the mirror job
completes.

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