From b9f4efe80f835a18bad3e3ab3333866a704d3ef6 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Tue, 1 Sep 2026 14:43:57 +0000 Subject: [PATCH] fix(ci): edit PRs through REST so the bot token does not need read:org gh pr edit resolves the PR through a GraphQL query that selects the review requests, whose User.login / Team.name / Team.slug fields classic PATs gate behind read:org. AZTEC_BOT_GITHUB_TOKEN carries only repo + workflow, so every gh pr edit run under it is rejected before the edit is attempted. The equivalent REST endpoints need only the repo scope. --- .github/ci3.sh | 4 +++- .github/ci3_success.sh | 8 +++++++- .github/workflows/ci3-external.yml | 6 ++++-- .github/workflows/ci3.yml | 16 ++++++++++++---- scripts/merge-train/update-pr-body.sh | 7 ++++++- scripts/merge-train/wakeup-prs.sh | 5 ++++- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/.github/ci3.sh b/.github/ci3.sh index b012bd32eeba..dab48a594fee 100755 --- a/.github/ci3.sh +++ b/.github/ci3.sh @@ -81,7 +81,9 @@ function handle_release_pr { git tag "${tag_name}" git push origin "${tag_name}" echo "Created and pushed tag: ${tag_name}" - gh pr edit $PR_NUMBER --remove-label ci-release-pr || true + # REST, not `gh pr edit --remove-label`: the latter's GraphQL query needs the `read:org` scope + # AZTEC_BOT_GITHUB_TOKEN does not carry. + gh api -X DELETE "repos/${github_repository}/issues/${PR_NUMBER}/labels/ci-release-pr" || true } function main { diff --git a/.github/ci3_success.sh b/.github/ci3_success.sh index 7684cdd71410..f31839ea004f 100755 --- a/.github/ci3_success.sh +++ b/.github/ci3_success.sh @@ -30,13 +30,19 @@ function handle_squash_merge { # Reauth the git repo with our GITHUB_TOKEN git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${github_repository} git config --unset-all http.https://github.com/.extraheader || true + # Drop the trigger label BEFORE squashing. squash-pr.sh force-pushes, which fires a fresh + # `synchronize` event; if the label were still set, that event would re-enter this path and loop. + # Use the REST endpoint (needs only the `repo` scope AZTEC_BOT_GITHUB_TOKEN has) rather than + # `gh pr edit --remove-label`, whose GraphQL query requires the `read:org` scope the token lacks, + # so the label would never be cleared. `|| true` stops a transient failure from aborting the + # merge; squash-pr.sh's single-commit guard prevents looping regardless. + gh api -X DELETE "repos/${github_repository}/issues/${PR_NUMBER}/labels/ci-squash-and-merge" || true # Get the base commit (merge-base) for the PR ./scripts/merge-train/squash-pr.sh \ "${PR_NUMBER}" \ "${PR_HEAD_REF}" \ "${PR_BASE_REF}" \ "${PR_BASE_SHA}" - gh pr edit "${PR_NUMBER}" --remove-label "ci-squash-and-merge" gh pr merge "${PR_NUMBER}" --auto -m || true echo "Squash and merge completed" } diff --git a/.github/workflows/ci3-external.yml b/.github/workflows/ci3-external.yml index 32b130c6a7c5..467f4679e052 100644 --- a/.github/workflows/ci3-external.yml +++ b/.github/workflows/ci3-external.yml @@ -48,18 +48,20 @@ jobs: GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} run: | set -o pipefail + # Labels are stripped through REST: `gh pr edit --remove-label` runs a GraphQL query + # needing the `read:org` scope AZTEC_BOT_GITHUB_TOKEN does not carry. # Note: pipe to jq with -s add to flatten paginated arrays; gh's own --jq runs per-page. labeller=$(gh api --paginate "/repos/$REPO/issues/$PR_NUMBER/events" \ | jq -r -s 'add | [.[] | select(.event == "labeled" and .label.name == "ci-external")] | last | .actor.login // empty') if [ -z "$labeller" ] || [ "$labeller" = "null" ]; then echo "Error: could not determine who applied the 'ci-external' label. Stripping it." - gh pr edit "$PR_NUMBER" --remove-label "ci-external" || true + gh api -X DELETE "/repos/$REPO/issues/$PR_NUMBER/labels/ci-external" || true exit 1 fi state=$(gh api "/orgs/AztecProtocol/teams/watchers/memberships/$labeller" --jq '.state' 2>/dev/null || true) if [ "$state" != "active" ]; then echo "Error: labeller is not an active member of AztecProtocol/watchers. Stripping 'ci-external' label." - gh pr edit "$PR_NUMBER" --remove-label "ci-external" + gh api -X DELETE "/repos/$REPO/issues/$PR_NUMBER/labels/ci-external" || true exit 1 fi echo "Labeller is authorized." diff --git a/.github/workflows/ci3.yml b/.github/workflows/ci3.yml index 1d5866d36b61..3c8b928c603c 100644 --- a/.github/workflows/ci3.yml +++ b/.github/workflows/ci3.yml @@ -54,7 +54,9 @@ jobs: if: contains(github.event.pull_request.labels.*.name, 'ci-wakeup-pr-after-merge') env: GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} - run: gh pr edit ${{ github.event.pull_request.number }} --remove-label ci-wakeup-pr-after-merge --repo ${{ github.repository }} || true + # REST, not `gh pr edit --remove-label`: the latter's GraphQL query needs the `read:org` + # scope AZTEC_BOT_GITHUB_TOKEN does not carry. + run: gh api -X DELETE "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/ci-wakeup-pr-after-merge" || true # Do NOT bump actions/checkout to v6 here. v6 ("persist creds to a separate file", #2286) # stores the persisted github.token in a temp credentials file pulled in via includeIf, which @@ -233,7 +235,9 @@ jobs: if: github.event.pull_request && contains(github.event.pull_request.labels.*.name, 'ci-network-scenario') env: GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} - run: gh pr edit ${{ github.event.pull_request.number }} --remove-label ci-network-scenario --repo ${{ github.repository }} || true + # REST, not `gh pr edit --remove-label`: the latter's GraphQL query needs the `read:org` + # scope AZTEC_BOT_GITHUB_TOKEN does not carry. + run: gh api -X DELETE "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/ci-network-scenario" || true - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 @@ -366,7 +370,9 @@ jobs: - name: Remove label (one-time use) env: GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} - run: gh pr edit ${{ github.event.pull_request.number }} --remove-label ci-network-bench --repo ${{ github.repository }} || true + # REST, not `gh pr edit --remove-label`: the latter's GraphQL query needs the `read:org` + # scope AZTEC_BOT_GITHUB_TOKEN does not carry. + run: gh api -X DELETE "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/ci-network-bench" || true - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 @@ -448,7 +454,9 @@ jobs: - name: Remove label env: GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} - run: gh pr edit ${{ github.event.pull_request.number }} --remove-label ci-network-kind --repo ${{ github.repository }} + # REST, not `gh pr edit --remove-label`: the latter's GraphQL query needs the `read:org` + # scope AZTEC_BOT_GITHUB_TOKEN does not carry. + run: gh api -X DELETE "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/ci-network-kind" || true - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 diff --git a/scripts/merge-train/update-pr-body.sh b/scripts/merge-train/update-pr-body.sh index 6b780f9a84cc..4c5aa76cf271 100755 --- a/scripts/merge-train/update-pr-body.sh +++ b/scripts/merge-train/update-pr-body.sh @@ -53,6 +53,11 @@ BEGIN_COMMIT_OVERRIDE $formatted_commits END_COMMIT_OVERRIDE" -gh pr edit "$pr_number" --body "$new_body" +# Set the body through the REST endpoint, which needs only the `repo` scope AZTEC_BOT_GITHUB_TOKEN +# carries. `gh pr edit --body` instead runs a GraphQL query that pulls the PR's review requests, +# and the User/Team fields in that union require `read:org`, so the token is rejected with a scope +# error before the edit is ever attempted. +jq -n --arg body "$new_body" '{ body: $body }' \ + | gh api --method PATCH "repos/{owner}/{repo}/pulls/$pr_number" --input - >/dev/null echo "PR #$pr_number body updated" diff --git a/scripts/merge-train/wakeup-prs.sh b/scripts/merge-train/wakeup-prs.sh index 5739d109136e..0d385de66056 100755 --- a/scripts/merge-train/wakeup-prs.sh +++ b/scripts/merge-train/wakeup-prs.sh @@ -51,7 +51,10 @@ echo "$prs" | jq -c '.' | while IFS= read -r pr_json; do fi echo "PR #$pr_number: CI passed and automerge enabled, adding wakeup label" - gh pr edit "$pr_number" --add-label "ci-wakeup-pr-after-merge" || { + # REST, not `gh pr edit --add-label`: the latter's GraphQL query needs the `read:org` scope + # AZTEC_BOT_GITHUB_TOKEN does not carry. + jq -n '{ labels: ["ci-wakeup-pr-after-merge"] }' \ + | gh api --method POST "repos/{owner}/{repo}/issues/$pr_number/labels" --input - >/dev/null || { echo "WARNING: Failed to add label to PR #$pr_number" } done