From 95962c98646f39a236739d35c64e73c39fa24ae6 Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Tue, 11 Aug 2026 10:57:00 -0400 Subject: [PATCH 1/3] Migrate pull_request_target workflows --- .github/workflows/check-change-note.yml | 5 +- .github/workflows/labeler-apply.yml | 88 +++++++++++++++++++++++++ .github/workflows/labeler.yml | 13 ++-- 3 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/labeler-apply.yml diff --git a/.github/workflows/check-change-note.yml b/.github/workflows/check-change-note.yml index 70b78ce72944..d29bd00486be 100644 --- a/.github/workflows/check-change-note.yml +++ b/.github/workflows/check-change-note.yml @@ -1,10 +1,11 @@ name: Check change note permissions: + contents: read pull-requests: read on: - pull_request_target: + pull_request: types: [labeled, unlabeled, opened, synchronize, reopened, ready_for_review] paths: - "*/ql/src/**/*.ql" @@ -23,7 +24,7 @@ jobs: env: REPO: ${{ github.repository }} PULL_REQUEST_NUMBER: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/labeler-apply.yml b/.github/workflows/labeler-apply.yml new file mode 100644 index 000000000000..bd8c15a081aa --- /dev/null +++ b/.github/workflows/labeler-apply.yml @@ -0,0 +1,88 @@ +name: "Pull Request Labeler Apply" + +on: + workflow_run: + workflows: ["Pull Request Labeler"] + types: [completed] + +permissions: {} + +jobs: + triage: + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + pull-requests: write + steps: + - name: Validate pull request from workflow_run + id: validate + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.event.workflow_run.id }} + run: | + set -euo pipefail + + run_json=$(gh api "repos/$REPO/actions/runs/$RUN_ID") + event=$(jq -r '.event' <<<"$run_json") + conclusion=$(jq -r '.conclusion' <<<"$run_json") + head_sha=$(jq -r '.head_sha' <<<"$run_json") + head_repo=$(jq -r '.head_repository.full_name // empty' <<<"$run_json") + + if [ "$event" != "pull_request" ] || [ "$conclusion" != "success" ]; then + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + pr_number=$(jq -r 'if (.pull_requests | length) == 1 then .pull_requests[0].number else empty end' <<<"$run_json") + + if [ -z "$pr_number" ]; then + prs_json=$(gh api -H 'Accept: application/vnd.github+json' "repos/$REPO/commits/$head_sha/pulls") + pr_number=$(jq -r 'map(select(.state == "open")) | if length == 1 then .[0].number else empty end' <<<"$prs_json") + fi + + if [ -z "$pr_number" ]; then + head_owner=$(jq -r '.head_repository.owner.login // empty' <<<"$run_json") + head_branch=$(jq -r '.head_branch // empty' <<<"$run_json") + if [ -n "$head_owner" ] && [ -n "$head_branch" ]; then + prs_json=$(gh api --method GET "repos/$REPO/pulls" -f state=open -f head="$head_owner:$head_branch") + pr_number=$(jq -r 'if length == 1 then .[0].number else empty end' <<<"$prs_json") + fi + fi + + if [ -z "$pr_number" ]; then + echo "Could not identify a unique open pull request for workflow run $RUN_ID; skipping." + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + pr_json=$(gh api "repos/$REPO/pulls/$pr_number") + state=$(jq -r '.state' <<<"$pr_json") + base_repo=$(jq -r '.base.repo.full_name' <<<"$pr_json") + pr_head_sha=$(jq -r '.head.sha' <<<"$pr_json") + pr_head_repo=$(jq -r '.head.repo.full_name // empty' <<<"$pr_json") + + if [ "$state" != "open" ] || [ "$base_repo" != "$REPO" ]; then + echo "Pull request #$pr_number is no longer an open pull request against $REPO; skipping." + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$pr_head_sha" != "$head_sha" ] || [ "$pr_head_repo" != "$head_repo" ]; then + echo "Pull request #$pr_number changed since workflow run $RUN_ID; skipping stale labeling." + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "number=$pr_number" >> "$GITHUB_OUTPUT" + echo "apply=true" >> "$GITHUB_OUTPUT" + + - uses: actions/labeler@v4 + if: steps.validate.outputs.apply == 'true' + with: + repo-token: "${{ github.token }}" + pr-number: ${{ steps.validate.outputs.number }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 512fa40d2e3a..f6f54382f9b8 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,15 +1,16 @@ name: "Pull Request Labeler" + on: -- pull_request_target + pull_request: + types: [opened, synchronize, reopened] permissions: contents: read - pull-requests: write + pull-requests: read jobs: - triage: + request-labels: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Request privileged labeling + run: echo "Labels are applied by the workflow_run writer after re-validating the pull request." From 862b71a42e095146294c8fceebb2a8875e223fd0 Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Tue, 11 Aug 2026 11:08:29 -0400 Subject: [PATCH 2/3] Make labeler workflow_run PR lookup robust Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/labeler-apply.yml | 30 ++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/labeler-apply.yml b/.github/workflows/labeler-apply.yml index bd8c15a081aa..8f75b00c7244 100644 --- a/.github/workflows/labeler-apply.yml +++ b/.github/workflows/labeler-apply.yml @@ -41,16 +41,16 @@ jobs: pr_number=$(jq -r 'if (.pull_requests | length) == 1 then .pull_requests[0].number else empty end' <<<"$run_json") if [ -z "$pr_number" ]; then - prs_json=$(gh api -H 'Accept: application/vnd.github+json' "repos/$REPO/commits/$head_sha/pulls") - pr_number=$(jq -r 'map(select(.state == "open")) | if length == 1 then .[0].number else empty end' <<<"$prs_json") + prs_json=$(gh api -H 'Accept: application/vnd.github+json' "repos/$REPO/commits/$head_sha/pulls" || echo '[]') + pr_number=$(jq -r 'map(select(.state == "open")) | if length == 1 then .[0].number else empty end' <<<"$prs_json" 2>/dev/null || true) fi if [ -z "$pr_number" ]; then head_owner=$(jq -r '.head_repository.owner.login // empty' <<<"$run_json") head_branch=$(jq -r '.head_branch // empty' <<<"$run_json") if [ -n "$head_owner" ] && [ -n "$head_branch" ]; then - prs_json=$(gh api --method GET "repos/$REPO/pulls" -f state=open -f head="$head_owner:$head_branch") - pr_number=$(jq -r 'if length == 1 then .[0].number else empty end' <<<"$prs_json") + prs_json=$(gh api --method GET "repos/$REPO/pulls" -f state=open -f head="$head_owner:$head_branch" || echo '[]') + pr_number=$(jq -r 'if length == 1 then .[0].number else empty end' <<<"$prs_json" 2>/dev/null || true) fi fi @@ -60,11 +60,23 @@ jobs: exit 0 fi - pr_json=$(gh api "repos/$REPO/pulls/$pr_number") - state=$(jq -r '.state' <<<"$pr_json") - base_repo=$(jq -r '.base.repo.full_name' <<<"$pr_json") - pr_head_sha=$(jq -r '.head.sha' <<<"$pr_json") - pr_head_repo=$(jq -r '.head.repo.full_name // empty' <<<"$pr_json") + pr_json=$(gh api "repos/$REPO/pulls/$pr_number" || true) + if [ -z "$pr_json" ]; then + echo "Pull request #$pr_number could not be fetched; skipping." + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + state=$(jq -r '.state // empty' <<<"$pr_json" 2>/dev/null || true) + base_repo=$(jq -r '.base.repo.full_name // empty' <<<"$pr_json" 2>/dev/null || true) + pr_head_sha=$(jq -r '.head.sha // empty' <<<"$pr_json" 2>/dev/null || true) + pr_head_repo=$(jq -r '.head.repo.full_name // empty' <<<"$pr_json" 2>/dev/null || true) + + if [ -z "$state" ]; then + echo "Pull request #$pr_number could not be parsed; skipping." + echo "apply=false" >> "$GITHUB_OUTPUT" + exit 0 + fi if [ "$state" != "open" ] || [ "$base_repo" != "$REPO" ]; then echo "Pull request #$pr_number is no longer an open pull request against $REPO; skipping." From a014cd540873e2eff4fdf992ceaba755e0bdc1da Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Wed, 12 Aug 2026 09:54:38 -0400 Subject: [PATCH 3/3] Reconcile pull request labels from the default branch Replace the fork-approval-gated dispatcher with frequent trusted scheduled reconciliation and stable hourly recovery shards. Revalidate each open pull request and head SHA immediately before applying labels without checking out pull request code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 512eb347-ec89-4250-8bf1-87048974b01d --- .github/workflows/labeler-apply.yml | 100 ------------------- .github/workflows/labeler.yml | 144 ++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 108 deletions(-) delete mode 100644 .github/workflows/labeler-apply.yml diff --git a/.github/workflows/labeler-apply.yml b/.github/workflows/labeler-apply.yml deleted file mode 100644 index 8f75b00c7244..000000000000 --- a/.github/workflows/labeler-apply.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: "Pull Request Labeler Apply" - -on: - workflow_run: - workflows: ["Pull Request Labeler"] - types: [completed] - -permissions: {} - -jobs: - triage: - if: > - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - pull-requests: write - steps: - - name: Validate pull request from workflow_run - id: validate - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.event.workflow_run.id }} - run: | - set -euo pipefail - - run_json=$(gh api "repos/$REPO/actions/runs/$RUN_ID") - event=$(jq -r '.event' <<<"$run_json") - conclusion=$(jq -r '.conclusion' <<<"$run_json") - head_sha=$(jq -r '.head_sha' <<<"$run_json") - head_repo=$(jq -r '.head_repository.full_name // empty' <<<"$run_json") - - if [ "$event" != "pull_request" ] || [ "$conclusion" != "success" ]; then - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - pr_number=$(jq -r 'if (.pull_requests | length) == 1 then .pull_requests[0].number else empty end' <<<"$run_json") - - if [ -z "$pr_number" ]; then - prs_json=$(gh api -H 'Accept: application/vnd.github+json' "repos/$REPO/commits/$head_sha/pulls" || echo '[]') - pr_number=$(jq -r 'map(select(.state == "open")) | if length == 1 then .[0].number else empty end' <<<"$prs_json" 2>/dev/null || true) - fi - - if [ -z "$pr_number" ]; then - head_owner=$(jq -r '.head_repository.owner.login // empty' <<<"$run_json") - head_branch=$(jq -r '.head_branch // empty' <<<"$run_json") - if [ -n "$head_owner" ] && [ -n "$head_branch" ]; then - prs_json=$(gh api --method GET "repos/$REPO/pulls" -f state=open -f head="$head_owner:$head_branch" || echo '[]') - pr_number=$(jq -r 'if length == 1 then .[0].number else empty end' <<<"$prs_json" 2>/dev/null || true) - fi - fi - - if [ -z "$pr_number" ]; then - echo "Could not identify a unique open pull request for workflow run $RUN_ID; skipping." - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - pr_json=$(gh api "repos/$REPO/pulls/$pr_number" || true) - if [ -z "$pr_json" ]; then - echo "Pull request #$pr_number could not be fetched; skipping." - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - state=$(jq -r '.state // empty' <<<"$pr_json" 2>/dev/null || true) - base_repo=$(jq -r '.base.repo.full_name // empty' <<<"$pr_json" 2>/dev/null || true) - pr_head_sha=$(jq -r '.head.sha // empty' <<<"$pr_json" 2>/dev/null || true) - pr_head_repo=$(jq -r '.head.repo.full_name // empty' <<<"$pr_json" 2>/dev/null || true) - - if [ -z "$state" ]; then - echo "Pull request #$pr_number could not be parsed; skipping." - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ "$state" != "open" ] || [ "$base_repo" != "$REPO" ]; then - echo "Pull request #$pr_number is no longer an open pull request against $REPO; skipping." - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ "$pr_head_sha" != "$head_sha" ] || [ "$pr_head_repo" != "$head_repo" ]; then - echo "Pull request #$pr_number changed since workflow run $RUN_ID; skipping stale labeling." - echo "apply=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "number=$pr_number" >> "$GITHUB_OUTPUT" - echo "apply=true" >> "$GITHUB_OUTPUT" - - - uses: actions/labeler@v4 - if: steps.validate.outputs.apply == 'true' - with: - repo-token: "${{ github.token }}" - pr-number: ${{ steps.validate.outputs.number }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f6f54382f9b8..e3f464331790 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,16 +1,144 @@ name: "Pull Request Labeler" on: - pull_request: - types: [opened, synchronize, reopened] + schedule: + # Reconcile recently updated PRs promptly, including unapproved forks and + # conflicted PRs for which pull_request workflows do not run. + - cron: "7,22,37,52 * * * *" + # Reconcile one stable shard of all open PRs each hour to recover from + # delayed or missed scheduled runs. + - cron: "12 * * * *" + workflow_dispatch: + inputs: + pr_number: + description: "Open pull request number to reconcile" + required: true + type: string -permissions: - contents: read - pull-requests: read +permissions: {} + +concurrency: + group: pull-request-labeler + cancel-in-progress: false jobs: - request-labels: + triage: + if: github.ref_name == github.event.repository.default_branch runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + pull-requests: write steps: - - name: Request privileged labeling - run: echo "Labels are applied by the workflow_run writer after re-validating the pull request." + - uses: actions/checkout@v5 + with: + persist-credentials: false + sparse-checkout: .github/labeler.yml + sparse-checkout-cone-mode: false + + - name: Collect pull requests to reconcile + id: collect + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + EVENT_NAME: ${{ github.event_name }} + SCHEDULE: ${{ github.event.schedule }} + REQUESTED_PR: ${{ inputs.pr_number }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then + if [[ ! "$REQUESTED_PR" =~ ^[1-9][0-9]*$ ]]; then + echo "Invalid pull request number: $REQUESTED_PR" + exit 1 + fi + + pr_json=$(gh api "repos/$REPO/pulls/$REQUESTED_PR") + candidates=$(jq -c '[{ + number: .number, + head_sha: .head.sha + }]' <<<"$pr_json") + else + pulls_json=$(gh api --paginate \ + "repos/$REPO/pulls?state=open&sort=updated&direction=desc&per_page=100" | + jq -cs 'add') + + if [ "$SCHEDULE" = "12 * * * *" ]; then + shard=$(( ($(date -u +%s) / 3600) % 6 )) + candidates=$(jq -c --argjson shard "$shard" \ + '[.[] | select((.number % 6) == $shard) | { + number: .number, + head_sha: .head.sha + }]' <<<"$pulls_json") + else + cutoff=$(date -u -d "1 hour ago" "+%Y-%m-%dT%H:%M:%SZ") + # Hourly shards reconcile any candidates beyond this API budget. + candidates=$(jq -c --arg cutoff "$cutoff" \ + '[.[] | select(.updated_at >= $cutoff) | { + number: .number, + head_sha: .head.sha + }][0:100]' <<<"$pulls_json") + fi + fi + + echo "Collected $(jq 'length' <<<"$candidates") pull request(s)." + { + echo "candidates<> "$GITHUB_OUTPUT" + + - name: Validate pull request state + id: validate + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + CANDIDATES: ${{ steps.collect.outputs.candidates }} + run: | + set -euo pipefail + + valid_numbers=() + while IFS=$'\t' read -r pr_number expected_sha; do + if [[ ! "$pr_number" =~ ^[1-9][0-9]*$ ]] || + [[ ! "$expected_sha" =~ ^[0-9a-f]{40}$ ]]; then + echo "Skipping malformed pull request candidate." + continue + fi + + if ! pr_json=$(gh api "repos/$REPO/pulls/$pr_number"); then + echo "Pull request #$pr_number could not be fetched; skipping." + continue + fi + + if ! jq -e \ + --arg repo "$REPO" \ + --arg sha "$expected_sha" \ + '.state == "open" and + .base.repo.full_name == $repo and + .head.sha == $sha and + (.head.repo.full_name | type == "string")' \ + >/dev/null <<<"$pr_json"; then + echo "Pull request #$pr_number changed or is no longer open; skipping." + continue + fi + + valid_numbers+=("$pr_number") + done < <(jq -r '.[] | [.number, .head_sha] | @tsv' <<<"$CANDIDATES") + + if [ "${#valid_numbers[@]}" -eq 0 ]; then + echo "has_prs=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + { + echo "has_prs=true" + echo "pr_numbers<> "$GITHUB_OUTPUT" + + - uses: actions/labeler@v4 + if: steps.validate.outputs.has_prs == 'true' + with: + repo-token: "${{ github.token }}" + pr-number: ${{ steps.validate.outputs.pr_numbers }}