Skip to content

Commit 3355bdc

Browse files
fix(ci): fix rerun workflows for fork PRs (#13512)
1 parent 4a9ba90 commit 3355bdc

7 files changed

Lines changed: 148 additions & 113 deletions

File tree

.github/workflows/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,19 @@ See below for a summary of this repo's Actions
106106
- Runs the E2E tests for C3.
107107
- Cloudflare API credentials are only passed on Version Packages PRs (`changeset-release/main`), in the merge queue, or when the `run-remote-tests` label is applied. Other PRs run the E2E suite without remote tests.
108108

109+
### Rerun Code Owners (rerun-codeowners.yml + rerun-codeowners-privileged.yml)
110+
111+
- Triggers
112+
- A review is submitted or dismissed on a PR.
113+
- Actions
114+
- Re-runs the "Run Codeowners Plus" check so it re-evaluates approval status after the review change.
115+
- Uses the `workflow_run` pattern: the trigger workflow exists solely to fire a `workflow_run` event; the privileged companion workflow (which has full permissions) reads the PR head SHA from `github.event.workflow_run.head_sha` and performs the re-run. This is necessary because `pull_request_review` gives a read-only token for fork PRs and has no `_target` variant.
116+
109117
### Rerun Remote Tests (rerun-remote-tests.yml)
110118

111119
- Triggers
112-
- The `run-remote-tests` label is added to a PR.
120+
- The `run-remote-tests` or `run-c3-frameworks-tests` label is added to or removed from a PR.
113121
- Actions
114-
- Re-runs the Wrangler, Vite, and C3 E2E workflows for the PR so they pick up the label and pass API credentials to the test steps.
122+
- Re-runs the E2E workflows for the PR so they pick up the label change and pass (or withhold) API credentials to the test steps.
123+
- `run-remote-tests` re-runs Wrangler, Vite, and C3 E2E workflows; `run-c3-frameworks-tests` re-runs only C3 E2E.
124+
- Uses `pull_request_target` to get a privileged token even for fork PRs (safe because no untrusted code is checked out).

.github/workflows/codeowners.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: "Code Owners"
22

33
# Re-evaluate when PRs are opened/updated.
4-
# When reviews are submitted/dismissed, the separate rerun_codeowners.yml workflow
4+
# When reviews are submitted/dismissed, the separate rerun-codeowners.yml workflow
55
# re-runs this check (rather than creating a second check context).
66
# Using pull_request_target (not pull_request) so the workflow has access to secrets
77
# for fork PRs. This is safe because:
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: "Rerun Code Owners (Privileged)"
2+
3+
# Privileged companion to rerun-codeowners.yml.
4+
#
5+
# Runs after the "Rerun Code Owners" trigger workflow completes. The
6+
# workflow_run event always executes from the default branch with full
7+
# permissions — this is what allows us to call the Actions API even when the
8+
# original event (pull_request_review) came from a fork PR, where the
9+
# GITHUB_TOKEN would otherwise be read-only.
10+
#
11+
# The PR head SHA is read from github.event.workflow_run.head_sha — this is
12+
# GitHub-provided metadata and cannot be manipulated by fork code (unlike
13+
# the previous artifact-based approach).
14+
#
15+
# Strategy: CHECK-NAME LOOKUP
16+
# ----------------------------
17+
# The codeowners.yml workflow uses pull_request_target, which means its
18+
# workflow runs are indexed by the BASE branch SHA (e.g. main), NOT the PR
19+
# head SHA. So we cannot find the run via:
20+
#
21+
# GET /actions/workflows/codeowners.yml/runs?head_sha=<PR_HEAD>
22+
#
23+
# That query returns nothing because the run's head_sha is main's HEAD.
24+
#
25+
# However, the codeowners-plus action creates its CHECK RUN on the PR head
26+
# commit (so the status appears on the PR). The check-runs API lets us look
27+
# up by check name + commit SHA:
28+
#
29+
# GET /commits/<PR_HEAD>/check-runs?check_name=Run+Codeowners+Plus
30+
#
31+
# From the check run's details_url we extract the Actions job ID, then
32+
# re-run that specific job.
33+
on:
34+
workflow_run:
35+
workflows: ["Rerun Code Owners"]
36+
types: [completed]
37+
38+
permissions: {}
39+
40+
jobs:
41+
rerun-codeowners:
42+
name: "Rerun Codeowners Plus"
43+
runs-on: ubuntu-latest
44+
if: github.event.workflow_run.conclusion == 'success'
45+
permissions:
46+
actions: write
47+
checks: read
48+
steps:
49+
- name: Re-run Codeowners check
50+
env:
51+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
53+
REPO: ${{ github.repository }}
54+
run: |
55+
# Find the "Run Codeowners Plus" check run on the PR head commit
56+
# and extract the Actions job ID from the check run's details URL
57+
# (the last path segment before any query string).
58+
job_id=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs?check_name=Run+Codeowners+Plus" \
59+
--jq '(.check_runs[0].details_url // "") | split("/") | last | split("?") | first' || true)
60+
61+
if [ -n "$job_id" ]; then
62+
gh api "repos/${REPO}/actions/jobs/${job_id}/rerun" --method POST \
63+
|| echo "::warning::Job ${job_id} may already be running"
64+
echo "Re-triggered 'Run Codeowners Plus' (job ${job_id})"
65+
else
66+
echo "::warning::Check 'Run Codeowners Plus' not found for SHA ${HEAD_SHA}"
67+
fi
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: "Rerun Code Owners"
2+
3+
# Trigger: a review is submitted or dismissed on a pull request.
4+
#
5+
# Goal: re-run the "Run Codeowners Plus" check from the main codeowners.yml
6+
# workflow (which is triggered by pull_request_target) so it re-evaluates
7+
# approval status after the review change.
8+
#
9+
# Why a two-workflow design?
10+
# -------------------------
11+
# For fork PRs, pull_request_review gives a read-only GITHUB_TOKEN and no
12+
# access to secrets. Unlike labeled/unlabeled events there is no
13+
# pull_request_review variant of pull_request_target, so we cannot get a
14+
# privileged token in a single workflow. Instead this workflow simply needs
15+
# to *exist and succeed* — the companion workflow
16+
# (rerun-codeowners-privileged.yml) is triggered by the workflow_run event
17+
# when THIS workflow completes. workflow_run always runs from the default
18+
# branch with full permissions, and it reads the PR head SHA directly from
19+
# github.event.workflow_run.head_sha (GitHub-provided metadata, not
20+
# controllable by fork code).
21+
on:
22+
pull_request_review:
23+
types: [submitted, dismissed]
24+
25+
permissions: {}
26+
27+
jobs:
28+
trigger:
29+
name: "Trigger Privileged Rerun"
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Log trigger
33+
run: echo "Review event on ${{ github.event.pull_request.html_url }} — privileged rerun will follow."
Lines changed: 34 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
name: "Rerun Remote Tests"
22

3-
# When the "run-remote-tests" label is added to or removed from a PR, re-run
4-
# the E2E workflows so they pick up the label change and pass (or withhold) the
5-
# Cloudflare API token to the test steps. This avoids adding "labeled" as a
6-
# trigger type to every E2E workflow (which would cause wasteful re-runs on
7-
# unrelated label changes).
3+
# Trigger: the "run-remote-tests" or "run-c3-frameworks-tests" label is added
4+
# to or removed from a pull request.
5+
#
6+
# Goal: re-run the E2E workflows (e2e-wrangler.yml, e2e-vite.yml, c3-e2e.yml)
7+
# so they pick up the label change and either pass or withhold the Cloudflare
8+
# API token to the test steps. This avoids adding "labeled" as a trigger type
9+
# to every E2E workflow (which would cause wasteful re-runs on unrelated label
10+
# changes).
11+
#
12+
# Using pull_request_target (not pull_request) so the workflow has access to a
13+
# privileged GITHUB_TOKEN even for fork PRs. This is safe because the workflow
14+
# runs code from the default branch — fork authors cannot modify it — and only
15+
# calls the Actions API (no checkout of untrusted code).
816
on:
9-
pull_request:
17+
pull_request_target:
1018
types: [labeled, unlabeled]
1119

1220
permissions: {}
1321

1422
jobs:
1523
rerun-e2e:
1624
name: "Rerun E2E Tests"
17-
if: github.event.label.name == 'run-remote-tests'
25+
if: github.event.label.name == 'run-remote-tests' || github.event.label.name == 'run-c3-frameworks-tests'
1826
runs-on: ubuntu-latest
1927
permissions:
2028
actions: write
@@ -25,30 +33,36 @@ jobs:
2533
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
2634
REPO: ${{ github.repository }}
2735
run: |
28-
for workflow in e2e-wrangler.yml e2e-vite.yml c3-e2e.yml; do
29-
run=$(gh api "repos/${REPO}/actions/workflows/${workflow}/runs?head_sha=${HEAD_SHA}&per_page=1" \
36+
# Determine which workflows to re-run based on the label.
37+
if [ "${{ github.event.label.name }}" = "run-remote-tests" ]; then
38+
WORKFLOWS="e2e-wrangler.yml e2e-vite.yml c3-e2e.yml"
39+
else
40+
WORKFLOWS="c3-e2e.yml"
41+
fi
42+
43+
for workflow in ${WORKFLOWS}; do
44+
# Find the most recent run of this workflow at the PR head SHA.
45+
run_info=$(gh api "repos/${REPO}/actions/workflows/${workflow}/runs?head_sha=${HEAD_SHA}&per_page=1" \
3046
--jq '.workflow_runs[0] | "\(.id) \(.status)"' || true)
3147
32-
run_id=$(echo "$run" | awk '{print $1}')
33-
status=$(echo "$run" | awk '{print $2}')
48+
run_id=$(echo "$run_info" | awk '{print $1}')
49+
status=$(echo "$run_info" | awk '{print $2}')
3450
3551
if [ -z "$run_id" ] || [ "$run_id" = "null" ]; then
36-
echo "No run found for ${workflow} at SHA ${HEAD_SHA}"
52+
echo "::warning::No run found for ${workflow} at SHA ${HEAD_SHA}"
3753
continue
3854
fi
3955
40-
# Each E2E workflow has a "check-remote" step that calls
41-
# `gh pr view ... --json labels` at runtime, so a run that
42-
# hasn't started yet will see the newly-added label on its
43-
# own — no need to cancel and re-run it.
56+
# Runs that haven't started yet will see the newly-added label
57+
# when they begin — no need to cancel and re-run them.
4458
if [ "$status" != "completed" ] && [ "$status" != "in_progress" ]; then
4559
echo "${workflow} run ${run_id} is ${status} — not yet started, skipping."
4660
continue
4761
fi
4862
49-
# If the run is actively executing, the label check has
50-
# already evaluated to false. Cancel it first — the rerun
51-
# endpoint only works on completed runs.
63+
# If the run is actively executing, the label check has already
64+
# evaluated (likely to the old value). Cancel it first — the
65+
# rerun endpoint only works on completed runs.
5266
if [ "$status" = "in_progress" ]; then
5367
echo "Cancelling in-progress ${workflow} run ${run_id}..."
5468
gh api "repos/${REPO}/actions/runs/${run_id}/cancel" --method POST || true
@@ -67,54 +81,5 @@ jobs:
6781
6882
gh api "repos/${REPO}/actions/runs/${run_id}/rerun" --method POST \
6983
&& echo "Re-triggered ${workflow} (run ${run_id})" \
70-
|| echo "Failed to re-run ${workflow} (run ${run_id})"
84+
|| echo "::warning::Failed to re-run ${workflow} (run ${run_id})"
7185
done
72-
73-
rerun-c3-frameworks:
74-
name: "Rerun C3 Frameworks E2E Tests"
75-
if: github.event.label.name == 'run-c3-frameworks-tests'
76-
runs-on: ubuntu-latest
77-
permissions:
78-
actions: write
79-
steps:
80-
- name: "Re-run C3 E2E workflow"
81-
env:
82-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83-
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
84-
REPO: ${{ github.repository }}
85-
run: |
86-
workflow=c3-e2e.yml
87-
run=$(gh api "repos/${REPO}/actions/workflows/${workflow}/runs?head_sha=${HEAD_SHA}&per_page=1" \
88-
--jq '.workflow_runs[0] | "\(.id) \(.status)"' || true)
89-
90-
run_id=$(echo "$run" | awk '{print $1}')
91-
status=$(echo "$run" | awk '{print $2}')
92-
93-
if [ -z "$run_id" ] || [ "$run_id" = "null" ]; then
94-
echo "No run found for ${workflow} at SHA ${HEAD_SHA}"
95-
exit 0
96-
fi
97-
98-
if [ "$status" != "completed" ] && [ "$status" != "in_progress" ]; then
99-
echo "${workflow} run ${run_id} is ${status} — not yet started, skipping."
100-
exit 0
101-
fi
102-
103-
if [ "$status" = "in_progress" ]; then
104-
echo "Cancelling in-progress ${workflow} run ${run_id}..."
105-
gh api "repos/${REPO}/actions/runs/${run_id}/cancel" --method POST || true
106-
107-
for i in $(seq 1 30); do
108-
current=$(gh api "repos/${REPO}/actions/runs/${run_id}" --jq '.status')
109-
if [ "$current" = "completed" ]; then
110-
echo "Run ${run_id} is now completed (cancelled)."
111-
break
112-
fi
113-
echo " Waiting for cancellation to finish (${i}/30, status: ${current})..."
114-
sleep 2
115-
done
116-
fi
117-
118-
gh api "repos/${REPO}/actions/runs/${run_id}/rerun" --method POST \
119-
&& echo "Re-triggered ${workflow} (run ${run_id})" \
120-
|| echo "Failed to re-run ${workflow} (run ${run_id})"

.github/workflows/rerun_codeowners.yml

Lines changed: 0 additions & 40 deletions
This file was deleted.

CODEOWNERS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ This exists only so that GitHub branch protection can gate merging on the bot's
8686

8787
### `.github/workflows/codeowners.yml` — GitHub Actions Workflow
8888

89-
A single workflow handles PR events (`pull_request_target`). When reviews are submitted or dismissed, the separate `rerun_codeowners.yml` workflow re-runs the check.
89+
A single workflow handles PR events (`pull_request_target`). When reviews are submitted or dismissed, the `rerun-codeowners.yml` / `rerun-codeowners-privileged.yml` workflow pair re-runs the check (using the `workflow_run` pattern so it works for fork PRs too).
9090

9191
Using `pull_request_target` (not `pull_request`) ensures the workflow has access to secrets for **fork PRs**. The checkout is always the base branch, so PR authors cannot modify ownership rules.
9292

0 commit comments

Comments
 (0)