From 9d099e159ea196375ab937e3f2c1d12a64325958 Mon Sep 17 00:00:00 2001 From: Lukas Wallrich Date: Wed, 10 Jun 2026 19:13:20 +0100 Subject: [PATCH] ci: make PR checks fork-friendly Fork pull_request runs get a read-only GITHUB_TOKEN and no repo secrets, so steps that post PR comments or use the staging PAT fail (403/empty token) on every fork PR. Handle forks explicitly: - check_images / spell-check: keep posting the PR comment for same-repo PRs, but on fork PRs write the same report to the job summary instead of 403ing on the comment write. - staging-aggregate: fail fast on fork PRs with a clear note pointing maintainers at the manual single_pr workflow_dispatch, replacing the cryptic "Input required and not supplied: token" checkout error. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/check_images.yaml | 19 ++++++++++++++ .github/workflows/spell-check.yaml | 21 ++++++++++++++-- .github/workflows/staging-aggregate.yaml | 32 ++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check_images.yaml b/.github/workflows/check_images.yaml index 011f2223523..1b9b88101e7 100644 --- a/.github/workflows/check_images.yaml +++ b/.github/workflows/check_images.yaml @@ -46,7 +46,9 @@ jobs: run: | python scripts/webp_conversion/check_images_in_pr.py + # Same-repo PRs: the token can write, so post/update the result as a PR comment. - name: Find Comment + if: github.event.pull_request.head.repo.full_name == github.repository uses: peter-evans/find-comment@v4 id: fc with: @@ -55,9 +57,26 @@ jobs: body-includes: mage files/references - name: Create or update comment + if: github.event.pull_request.head.repo.full_name == github.repository uses: peter-evans/create-or-update-comment@v5 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: ${{ steps.image_check.outputs.comment }} edit-mode: replace + + # Fork PRs: the GITHUB_TOKEN is read-only, so a PR comment would 403. + # Surface the same result as a job-summary report in the workflow run instead. + - name: Image check report (fork PR) + if: github.event.pull_request.head.repo.full_name != github.repository + env: + REPORT: ${{ steps.image_check.outputs.comment }} + run: | + { + echo "## 🖼️ Image check report" + echo "" + echo "> This PR is from a fork, so the workflow cannot post a PR comment" + echo "> (fork runs receive a read-only token). The result is shown below." + echo "" + echo "$REPORT" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/spell-check.yaml b/.github/workflows/spell-check.yaml index 3e0d519ddfb..2dd1ff6e611 100644 --- a/.github/workflows/spell-check.yaml +++ b/.github/workflows/spell-check.yaml @@ -64,8 +64,9 @@ jobs: run: | python scripts/spell_check/check_spelling.py + # Same-repo PRs: the token can write, so post/update the result as a PR comment. - name: Find Comment - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository uses: peter-evans/find-comment@v4 id: fc with: @@ -74,10 +75,26 @@ jobs: body-includes: Spell Check - name: Create or update comment - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository uses: peter-evans/create-or-update-comment@v5 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: ${{ steps.spell_check.outputs.comment }} edit-mode: replace + + # Fork PRs: the GITHUB_TOKEN is read-only, so a PR comment would 403. + # Surface the same result as a job-summary report in the workflow run instead. + - name: Spell check report (fork PR) + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + env: + REPORT: ${{ steps.spell_check.outputs.comment }} + run: | + { + echo "## 📝 Spell check report" + echo "" + echo "> This PR is from a fork, so the workflow cannot post a PR comment" + echo "> (fork runs receive a read-only token). The result is shown below." + echo "" + echo "$REPORT" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/staging-aggregate.yaml b/.github/workflows/staging-aggregate.yaml index 810e6b7c4b0..f4a817d08b5 100644 --- a/.github/workflows/staging-aggregate.yaml +++ b/.github/workflows/staging-aggregate.yaml @@ -6,6 +6,14 @@ name: Staging Aggregate Deployment # Purpose: Aggregates and deploys staging changes to production # Triggers: PRs to main, monthly schedule, or manual dispatch # Features: Multi-PR aggregation, staging deployment, and force deploy option +# +# Fork PRs: GitHub does not pass repository secrets to runs triggered from a +# fork, so a fork PR cannot auto-deploy to staging (its run fails fast with a +# note instead of a cryptic token error). To preview a fork PR on staging, a +# maintainer runs this workflow manually (Actions -> Run workflow) from `main` +# and sets `single_pr` to the PR number. The run resolves the fork from the PR +# number and fetches it over its public URL, so there is no need to pick the +# fork branch in the dropdown. on: pull_request: @@ -57,6 +65,30 @@ jobs: total-prs: ${{ steps.aggregate.outputs.total_prs }} has-prs: ${{ steps.aggregate.outputs.has_prs }} steps: + # Fork PRs can't receive secrets, so the checkout/gh steps below would fail + # with a cryptic "Input required and not supplied: token". Fail fast with a + # clear note pointing maintainers at the manual single_pr dispatch instead. + - name: Fork PR — staging not auto-deployed + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + run: | + PR_NUM="${{ github.event.pull_request.number }}" + { + echo "## ⏭️ Fork PR — staging not auto-deployed" + echo "" + echo "GitHub does not pass repository secrets to runs triggered from a fork," + echo "so this workflow cannot auto-deploy a fork PR to staging." + echo "" + echo "**To preview this PR on staging**, a maintainer runs the *Staging Aggregate" + echo "Deployment* workflow manually (Actions → Run workflow) from \`main\` and sets:" + echo "" + echo " single_pr = ${PR_NUM}" + echo "" + echo "The run pulls this fork in by PR number over its public URL — no need to" + echo "select the fork branch in the dropdown." + } >> "$GITHUB_STEP_SUMMARY" + echo "::error::Fork PR #${PR_NUM} not auto-deployed. Maintainer: run this workflow manually with single_pr=${PR_NUM} to preview it on staging." + exit 1 + - name: Checkout repository uses: actions/checkout@v6 with: