From a931f1350e312e9a3d9e908b3f6c95f10a4fb110 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 23 Feb 2026 12:03:17 +0100 Subject: [PATCH 1/2] tools: automatically remove `lts-watch-*` labels --- .github/workflows/lint-release-proposal.yml | 4 +- .github/workflows/remove-lts-watch-label.yml | 28 +++++++ tools/actions/remove-lts-watch-label.sh | 85 ++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/remove-lts-watch-label.yml create mode 100755 tools/actions/remove-lts-watch-label.sh diff --git a/.github/workflows/lint-release-proposal.yml b/.github/workflows/lint-release-proposal.yml index 4ebeff30019b34..7e636a075c77e9 100644 --- a/.github/workflows/lint-release-proposal.yml +++ b/.github/workflows/lint-release-proposal.yml @@ -92,13 +92,11 @@ jobs: "/repos/${GITHUB_REPOSITORY}/compare/v${MAJOR}.x...$GITHUB_SHA" --paginate \ | node tools/actions/lint-release-proposal-commit-list.mjs "$CHANGELOG_PATH" "$GITHUB_SHA" \ | while IFS= read -r PR_URL; do - DONT_LAND_LABEL="dont-land-on-v${MAJOR}.x" LTS_WATCH_LABEL="lts-watch-v${MAJOR}.x" gh pr view \ + DONT_LAND_LABEL="dont-land-on-v${MAJOR}.x" gh pr view \ --json labels,url \ --jq ' if (.labels|any(.name==env.DONT_LAND_LABEL)) then error("\(.url) has the \(env.DONT_LAND_LABEL) label, forbidding it to be in this release proposal") - elif (.labels|any(.name==env.LTS_WATCH_LABEL)) then - error("\(.url) has the \(env.LTS_WATCH_LABEL) label, please remove the label now that the PR is included in a release proposal") end ' \ "$PR_URL" > /dev/null diff --git a/.github/workflows/remove-lts-watch-label.yml b/.github/workflows/remove-lts-watch-label.yml new file mode 100644 index 00000000000000..aaed9107a74ae1 --- /dev/null +++ b/.github/workflows/remove-lts-watch-label.yml @@ -0,0 +1,28 @@ +name: Remove `lts-watch-vXX.x` labels + +on: + push: + branches: + - v[0-9]+.x + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: false + +env: + NODE_VERSION: lts/* + +permissions: + contents: read + +jobs: + remove-lts-watch-labels: + runs-on: ubuntu-slim + permissions: + contents: read + pull-requests: write + steps: + - name: Parse commits and remove labels + run: tools/actions/remove-lts-watch-label.sh + env: + GH_TOKEN: ${{ github.token }} diff --git a/tools/actions/remove-lts-watch-label.sh b/tools/actions/remove-lts-watch-label.sh new file mode 100755 index 00000000000000..f4bc9f34fd96b0 --- /dev/null +++ b/tools/actions/remove-lts-watch-label.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +set -ex + +[ -n "$GITHUB_REPOSITORY_OWNER" ] || { + echo "Required environment variable GITHUB_REPOSITORY_OWNER not found" >&2 + exit 1 +} +[ -n "$GITHUB_REPOSITORY" ] || { + echo "Required environment variable GITHUB_REPOSITORY not found" >&2 + exit 1 +} +[ -n "$GITHUB_REF_NAME" ] || { + echo "Required environment variable GITHUB_REF_NAME not found" >&2 + exit 1 +} +[ -n "$GITHUB_EVENT_PATH" ] || { + echo "Required environment variable GITHUB_EVENT_PATH not found" >&2 + exit 1 +} + +owner=$GITHUB_REPOSITORY_OWNER +repo=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2) +label="lts-watch-$GITHUB_REF_NAME" + +# Lazy-get label node_id once +label_id= +fetch_label_id () { + # shellcheck disable=SC2016 + label_id="$(gh api graphql -f query=' + query($owner:String!, $repo:String!, $name:String!) { + repository(owner:$owner, name:$repo) { + label(name:$name) { id } + } + }' -f owner="$owner" -f repo="$repo" -f name="$label" --jq '.data.repository.label.id')" + + if [ -z "$label_id" ] || [ "$label_id" = "null" ]; then + echo "Could not resolve label id for '$label' in $owner/$repo" >&2 + exit 1 + fi +} + +jq -r '.commits[].message' < "$GITHUB_EVENT_PATH" \ +| while IFS= read -r msg; do + pr_url=$(echo "$msg" | git interpret-trailers --parse | awk -F': ' '$1 == "PR-URL" { print $2; exit }') + pr_number=${pr_url##"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/"} + + # Filter out pr_number that are not numbers (e.g. if PR-URL points to a different repo) + case "$pr_number" in + ''|*[!0-9]*) continue ;; + *) ;; + esac + + # shellcheck disable=SC2016 + pr_id="$(gh api graphql -f query=' + query($owner:String!, $repo:String!, $number:Int!, $label:String!) { + repository(owner:$owner, name:$repo) { + pullRequest(number:$number) { + id + labels(first: 1, query: $label) { + nodes { name } + } + } + } + }' -f owner="$owner" -f repo="$repo" -F number="$pr_number" -f label="$label" \ + --jq ' if (.data.repository.pullRequest.labels.nodes | length) then .data.repository.pullRequest.id end')" + + if [ -z "$pr_id" ] || [ "$pr_id" = "null" ]; then + echo "PR #$pr_number not found or does not have the label" >&2 + continue + fi + + [ -n "$label_id" ] || fetch_label_id + + # shellcheck disable=SC2016 + gh api graphql -f query=' + mutation($labelableId:ID!, $labelIds:[ID!]!) { + removeLabelsFromLabelable(input:{labelableId:$labelableId, labelIds:$labelIds}) { + clientMutationId + } + }' -f labelableId="$pr_id" -f labelIds="[$label_id]" >/dev/null + + # gentle throttle + sleep 0.2 +done From db651e346babf1aa2b2a1aa5c9d2623ac60a16df Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 23 Feb 2026 12:47:46 +0100 Subject: [PATCH 2/2] fixup! tools: automatically remove `lts-watch-*` labels --- tools/actions/remove-lts-watch-label.sh | 33 +++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tools/actions/remove-lts-watch-label.sh b/tools/actions/remove-lts-watch-label.sh index f4bc9f34fd96b0..596ad4655c7d81 100755 --- a/tools/actions/remove-lts-watch-label.sh +++ b/tools/actions/remove-lts-watch-label.sh @@ -2,6 +2,10 @@ set -ex +[ -n "$GITHUB_SERVER_URL" ] || { + echo "Required environment variable GITHUB_SERVER_URL not found" >&2 + exit 1 +} [ -n "$GITHUB_REPOSITORY_OWNER" ] || { echo "Required environment variable GITHUB_REPOSITORY_OWNER not found" >&2 exit 1 @@ -40,8 +44,8 @@ fetch_label_id () { fi } -jq -r '.commits[].message' < "$GITHUB_EVENT_PATH" \ -| while IFS= read -r msg; do +jq --raw-output0 '.commits[].message' < "$GITHUB_EVENT_PATH" \ +| while IFS= read -r -d '' msg; do pr_url=$(echo "$msg" | git interpret-trailers --parse | awk -F': ' '$1 == "PR-URL" { print $2; exit }') pr_number=${pr_url##"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/"} @@ -52,33 +56,30 @@ jq -r '.commits[].message' < "$GITHUB_EVENT_PATH" \ esac # shellcheck disable=SC2016 - pr_id="$(gh api graphql -f query=' - query($owner:String!, $repo:String!, $number:Int!, $label:String!) { + pr_id="$(LTS_WATCH_LABEL="$label" gh api graphql -f query=' + query($owner:String!, $repo:String!, $number:Int!) { repository(owner:$owner, name:$repo) { pullRequest(number:$number) { id - labels(first: 1, query: $label) { - nodes { name } - } + labels(first: 100) { nodes { name } } } } - }' -f owner="$owner" -f repo="$repo" -F number="$pr_number" -f label="$label" \ - --jq ' if (.data.repository.pullRequest.labels.nodes | length) then .data.repository.pullRequest.id end')" + }' -f owner="$owner" -f repo="$repo" -F number="$pr_number" \ + --jq 'if (.data.repository.pullRequest.labels.nodes | any(.name == env.LTS_WATCH_LABEL)) then .data.repository.pullRequest.id else null end')" - if [ -z "$pr_id" ] || [ "$pr_id" = "null" ]; then - echo "PR #$pr_number not found or does not have the label" >&2 - continue - fi + # Skip PRs that do not have the label + [ -n "$pr_id" ] || continue + # Lazy-load the label ID if not done already [ -n "$label_id" ] || fetch_label_id # shellcheck disable=SC2016 gh api graphql -f query=' - mutation($labelableId:ID!, $labelIds:[ID!]!) { - removeLabelsFromLabelable(input:{labelableId:$labelableId, labelIds:$labelIds}) { + mutation($labelableId:ID!, $labelId:ID!) { + removeLabelsFromLabelable(input:{labelableId:$labelableId, labelIds:[$labelId]}) { clientMutationId } - }' -f labelableId="$pr_id" -f labelIds="[$label_id]" >/dev/null + }' -f labelableId="$pr_id" -f labelId="$label_id" # gentle throttle sleep 0.2