This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 91c4ed4e7ed GH workflow validate-changelog: Make the /no-changelog 
command actually clear the failed changelog check (#5099)
91c4ed4e7ed is described below

commit 91c4ed4e7edda68f9fbc871ea302c44f63930725
Author: Jan Høydahl <[email protected]>
AuthorDate: Sat Oct 24 16:01:46 2026 +0200

    GH workflow validate-changelog: Make the /no-changelog command actually 
clear the failed changelog check (#5099)
---
 .github/workflows/changelog-comment-command.yml | 103 +++++++++++++++----
 .github/workflows/validate-changelog.yml        | 127 ++++++++++++------------
 dev-docs/changelog.adoc                         |   7 +-
 3 files changed, 147 insertions(+), 90 deletions(-)

diff --git a/.github/workflows/changelog-comment-command.yml 
b/.github/workflows/changelog-comment-command.yml
index 4d227b9f8cc..afad1968750 100644
--- a/.github/workflows/changelog-comment-command.yml
+++ b/.github/workflows/changelog-comment-command.yml
@@ -1,19 +1,15 @@
 name: Changelog Comment Commands
 
-# Lets a PR author (or any committer) toggle the 'no-changelog' label by
-# commenting on the PR, without needing the repo write access that setting a
-# label directly requires:
+# Lets a PR author or a committer toggle the 'no-changelog' label by 
commenting:
 #
 #   /no-changelog     - mark the PR as not needing a changelog entry
 #   /needs-changelog  - undo that
 #
-# The command applies/removes the label, and the label change re-triggers the
-# "Validate Changelog" workflow (which listens for labeled/unlabeled events),
-# so the red check turns green on its own.
+# The label change is followed by an explicit re-run of "Validate Changelog": a
+# label set with the GITHUB_TOKEN triggers no workflow of its own.
 #
-# This workflow deliberately never checks out or executes PR code: it only
-# reads the comment payload and calls the labels API, so running with a
-# write-scoped token is safe.
+# This workflow never checks out or executes PR code, and the comment body
+# reaches the shell only through 'env'.
 on:
   issue_comment:
     types:
@@ -22,14 +18,11 @@ on:
 concurrency:
   group: changelog-comment-command-${{ github.event.issue.number }}
 
-permissions:
-  issues: write        # add/remove labels, add a reaction, post a reply
-  pull-requests: write
+permissions: {}
 
 jobs:
   handle-command:
-    # Pull request comments only, from a human, that mention one of our 
commands.
-    # The exact (line-anchored) match happens in the parse step below.
+    # Pull request comments from a human; the exact match happens in the parse 
step.
     if: |
       github.event.issue.pull_request &&
       github.event.comment.user.type != 'Bot' &&
@@ -37,6 +30,13 @@ jobs:
        contains(github.event.comment.body, '/needs-changelog'))
     runs-on: ubuntu-latest
 
+    permissions:
+      issues: write        # add/remove labels, add a reaction, post a reply
+      pull-requests: write
+
+    outputs:
+      rerun: ${{ steps.apply.outputs.rerun }}
+
     steps:
       - name: Parse command
         id: parse
@@ -44,8 +44,7 @@ jobs:
           BODY: ${{ github.event.comment.body }}
         run: |
           set -euo pipefail
-          # A command must be alone on its own line, so that quoted text ("> 
/no-changelog")
-          # or a mention inside a sentence does not trigger it.
+          # A command must stand alone on its line, so quoted text or prose 
does not trigger it.
           command=none
           if printf '%s\n' "$BODY" | grep -iE 
'^[[:space:]]*/no-changelog[[:space:]]*\r?$' > /dev/null; then
             command=add
@@ -69,8 +68,7 @@ jobs:
           ASSOCIATION: ${{ github.event.comment.author_association }}
         run: |
           set -euo pipefail
-          # The PR author may always toggle it on their own PR; otherwise the
-          # commenter needs write access, which author_association reflects.
+          # The PR author may always toggle it; anyone else needs write access.
           if [ "$COMMENTER" = "$PR_AUTHOR" ]; then
             allowed=true
           else
@@ -83,6 +81,7 @@ jobs:
           echo "Commenter '${COMMENTER}' (${ASSOCIATION}) allowed: ${allowed}"
 
       - name: Apply label change
+        id: apply
         if: steps.authz.outputs.allowed == 'true' && 
(steps.parse.outputs.command == 'add' || steps.parse.outputs.command == 
'remove')
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -99,6 +98,7 @@ jobs:
           if [ "$COMMAND" = "remove" ]; then
             if [ "$has_label" = "true" ]; then
               gh api -X DELETE 
"repos/${REPO}/issues/${PR_NUMBER}/labels/${LABEL}" --silent
+              echo "rerun=true" >> "$GITHUB_OUTPUT"
               echo "::notice::Removed '${LABEL}' from PR#${PR_NUMBER}"
             else
               echo "::notice::PR#${PR_NUMBER} does not have '${LABEL}'; 
nothing to do"
@@ -106,12 +106,14 @@ jobs:
             exit 0
           fi
 
+          # Re-run even if the label is already set; the check can still be 
red.
           if [ "$has_label" = "true" ]; then
-            echo "::notice::PR#${PR_NUMBER} already has '${LABEL}'; nothing to 
do"
-            exit 0
+            echo "::notice::PR#${PR_NUMBER} already has '${LABEL}'"
+          else
+            gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/labels" -f 
"labels[]=${LABEL}" --silent
+            echo "::notice::Added '${LABEL}' to PR#${PR_NUMBER}"
           fi
-          gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/labels" -f 
"labels[]=${LABEL}" --silent
-          echo "::notice::Added '${LABEL}' to PR#${PR_NUMBER}"
+          echo "rerun=true" >> "$GITHUB_OUTPUT"
 
       - name: Acknowledge with a reaction
         if: steps.authz.outputs.allowed == 'true' && 
(steps.parse.outputs.command == 'add' || steps.parse.outputs.command == 
'remove')
@@ -148,3 +150,60 @@ jobs:
               > reply.md
           fi
           gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file reply.md
+
+  # Separate job, so the one handling the comment body never holds 'actions: 
write'.
+  rerun-check:
+    name: Re-run the changelog check
+    needs: handle-command
+    if: needs.handle-command.outputs.rerun == 'true'
+    runs-on: ubuntu-latest
+    continue-on-error: true
+
+    permissions:
+      actions: write       # re-run the Validate Changelog workflow
+      pull-requests: read  # read the PR's head SHA
+
+    steps:
+      - name: Re-run the latest Validate Changelog run
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          REPO: ${{ github.repository }}
+          PR_NUMBER: ${{ github.event.issue.number }}
+        run: |
+          set -euo pipefail
+          WORKFLOW=validate-changelog.yml
+
+          head_sha=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid 
--jq .headRefOid)
+
+          # An in-flight run may have read the labels before the change, and 
only a
+          # completed run can be re-run, so wait it out.
+          deadline=$(( $(date +%s) + 600 ))
+          while true; do
+            run=$(gh api 
"repos/${REPO}/actions/workflows/${WORKFLOW}/runs?head_sha=${head_sha}&per_page=1"
 \
+              --jq '.workflow_runs[0] // empty | "\(.id) \(.status) 
\(.conclusion)"')
+
+            if [ -z "$run" ]; then
+              echo "::notice::No ${WORKFLOW} run found for ${head_sha}; 
nothing to re-run"
+              exit 0
+            fi
+            read -r run_id run_status run_conclusion <<< "$run"
+            echo "Latest ${WORKFLOW} run for ${head_sha}: ${run_id} 
(${run_status}/${run_conclusion})"
+
+            # Approval starts a fresh run, which reads the label as it is now.
+            if [ "$run_conclusion" = "action_required" ]; then
+              echo "::notice::Run ${run_id} awaits workflow approval; the 
label applies once approved"
+              exit 0
+            fi
+            if [ "$run_status" = "completed" ]; then
+              break
+            fi
+            if [ "$(date +%s)" -ge "$deadline" ]; then
+              echo "::notice::Run ${run_id} is still ${run_status} after 
waiting; leaving it alone"
+              exit 0
+            fi
+            echo "Run ${run_id} is ${run_status}; waiting for it to finish"
+            sleep 15
+          done
+
+          gh api -X POST "repos/${REPO}/actions/runs/${run_id}/rerun" --silent
+          echo "::notice::Re-ran ${WORKFLOW} run ${run_id} for PR#${PR_NUMBER}"
diff --git a/.github/workflows/validate-changelog.yml 
b/.github/workflows/validate-changelog.yml
index af43d05b4d5..8f64cf32aeb 100644
--- a/.github/workflows/validate-changelog.yml
+++ b/.github/workflows/validate-changelog.yml
@@ -2,8 +2,8 @@ name: Validate Changelog
 
 on:
   pull_request:
-    # 'labeled'/'unlabeled' are included so that adding or removing the
-    # 'no-changelog' label re-runs this check on an already-failed PR.
+    # 'labeled'/'unlabeled' re-run this check when a human toggles 
'no-changelog';
+    # the comment command re-runs it explicitly, see 
changelog-comment-command.yml.
     types:
       - opened
       - reopened
@@ -17,12 +17,13 @@ concurrency:
   group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
github.ref }}
   cancel-in-progress: true
 
+permissions: {}
+
 jobs:
   validate-changelog:
     name: Check changelog entry
-    # Skip validation for Renovate PRs (solrbot); dependency changes are 
summarized at release time.
-    # On label events, only re-run for the 'no-changelog' label; other label 
churn
-    # (e.g. from the labeler workflow) leaves the existing check result 
untouched.
+    # Renovate PRs (solrbot) are summarized at release time, and of the label
+    # events only 'no-changelog' is of interest here.
     if: |
       github.event.pull_request.user.login != 'solrbot' &&
       (
@@ -31,46 +32,55 @@ jobs:
       )
     runs-on: ubuntu-latest
 
-    steps:
-    - name: Checkout code
-      uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
-      with:
-        fetch-depth: 0
+    permissions:
+      contents: read
+      issues: read         # the PR's labels
+      pull-requests: read  # the PR's changed files
+
+    # This job runs no PR code: it reads the PR through the API, keeps that 
data
+    # outside the workspace, and only runs the validator from the base commit.
+    env:
+      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      REPO: ${{ github.repository }}
+      PR_NUMBER: ${{ github.event.pull_request.number }}
+      HEAD_SHA: ${{ github.event.pull_request.head.sha }}
 
+    steps:
     - name: Check for no-changelog label
       id: check-label
-      env:
-        HAS_NO_CHANGELOG_LABEL: ${{ 
contains(github.event.pull_request.labels.*.name, 'no-changelog') }}
       run: |
-        if [ "$HAS_NO_CHANGELOG_LABEL" = "true" ]; then
-          echo "skip=true" >> "$GITHUB_OUTPUT"
-        else
-          echo "skip=false" >> "$GITHUB_OUTPUT"
+        set -euo pipefail
+        # Not github.event.pull_request.labels: a re-run replays the original 
payload.
+        has_label=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" \
+          --jq '[.[].name] | index("no-changelog") != null')
+        if [ "$has_label" = "true" ]; then
+          echo "::notice::PR#${PR_NUMBER} has the 'no-changelog' label; 
skipping validation"
         fi
+        echo "skip=${has_label}" >> "$GITHUB_OUTPUT"
 
     - name: Check for changelog entry
+      id: entry
       if: steps.check-label.outputs.skip == 'false'
       run: |
-        # Get the list of changed files
-        CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref 
}}...HEAD)
+        set -euo pipefail
+        files="$RUNNER_TEMP/pr-files.tsv"
+        entries="$RUNNER_TEMP/entry-paths.txt"
 
-        # Check if any files were added to changelog/unreleased/
-        if echo "$CHANGED_FILES" | grep -q "^changelog/unreleased/"; then
+        gh api --paginate "repos/${REPO}/pulls/${PR_NUMBER}/files" \
+          --jq '.[] | [.status, .filename] | @tsv' > "$files"
+
+        # Entries that are still there to validate; a deleted one has nothing 
to read.
+        awk -F'\t' '$1 != "removed" && $2 ~ 
/^changelog\/unreleased\/.+\.ya?ml$/ { print $2 }' \
+          "$files" > "$entries"
+
+        if [ -s "$entries" ]; then
           echo "✓ Changelog entry found"
+          echo "validate=true" >> "$GITHUB_OUTPUT"
           exit 0
         fi
 
-        # Check if only docs/tests/comments were changed (common exceptions)
-        HAS_NON_DOCS_CHANGES=false
-        while IFS= read -r file; do
-          # Skip changelog, docs, tests, CI config and certain config files
-          if ! echo "$file" | grep -qE 
"(^changelog/|^solr/solr-ref-guide/|^dev-docs/|^\.github/|\.md$|\.adoc$|^solr/.*/test|\.gradle$|\.properties$|README|NOTICE|LICENSE)";
 then
-            HAS_NON_DOCS_CHANGES=true
-            break
-          fi
-        done <<< "$CHANGED_FILES"
-
-        if [ "$HAS_NON_DOCS_CHANGES" = false ]; then
+        # Docs, tests, build files and CI config need no entry.
+        if ! cut -f2 "$files" | grep -qvE 
"(^changelog/|^solr/solr-ref-guide/|^dev-docs/|^\.github/|\.md$|\.adoc$|^solr/.*/test|\.gradle$|\.properties$|README|NOTICE|LICENSE)";
 then
           echo "✓ No code changes detected (docs/tests only)"
           exit 0
         fi
@@ -89,41 +99,30 @@ jobs:
         echo "This check then re-runs automatically. To undo, comment 
'/needs-changelog'."
         exit 1
 
+    # Only the validator script, only from the base commit, only one commit 
deep.
+    - name: Checkout the validator
+      if: steps.entry.outputs.validate == 'true'
+      uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+      with:
+        ref: ${{ github.event.pull_request.base.sha }}
+        sparse-checkout: dev-tools/scripts
+        fetch-depth: 1
+        persist-credentials: false
+
     - name: Validate changelog YAML structure
-      if: steps.check-label.outputs.skip == 'false'
+      if: steps.entry.outputs.validate == 'true'
       run: |
-        # Get the list of changed files
-        CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref 
}}...HEAD)
-
-        # Find all YAML files added to changelog/unreleased/
-        YAML_FILES=$(echo "$CHANGED_FILES" | grep 
"^changelog/unreleased/.*\.ya\?ml$" || true)
-
-        if [ -z "$YAML_FILES" ]; then
-          exit 0
-        fi
-
-        echo "Validating changelog YAML files..."
-        VALIDATION_FAILED=false
+        set -euo pipefail
+        entries="$RUNNER_TEMP/entry-paths.txt"
+        yamls="$RUNNER_TEMP/entries"
+        mkdir -p "$yamls"
 
+        # PR-authored data: fetched outside the workspace and only ever parsed.
         while IFS= read -r file; do
-          if [ -z "$file" ] || [ ! -f "$file" ]; then
-            continue
-          fi
+          echo "Fetching $file"
+          gh api -H "Accept: application/vnd.github.raw" \
+            "repos/${REPO}/contents/${file}?ref=${HEAD_SHA}" > 
"${yamls}/$(basename "$file")"
+        done < "$entries"
 
-          echo ""
-          echo "Validating: $file"
-
-          # Validate using a Python script
-          python3 dev-tools/scripts/validate-changelog-yaml.py "$file"
-
-          if [ $? -ne 0 ]; then
-            VALIDATION_FAILED=true
-          fi
-
-        done <<< "$YAML_FILES"
-
-        if [ "$VALIDATION_FAILED" = true ]; then
-          echo "Please see dev-docs/changelog.adoc for more info."
-
-          exit 1
-        fi
+        python3 dev-tools/scripts/validate-changelog-yaml.py "$yamls" \
+          || { echo "Please see dev-docs/changelog.adoc for more info."; exit 
1; }
diff --git a/dev-docs/changelog.adoc b/dev-docs/changelog.adoc
index ac6f0504ff5..05ae92728b5 100644
--- a/dev-docs/changelog.adoc
+++ b/dev-docs/changelog.adoc
@@ -122,10 +122,9 @@ TIP: Aliases for the `writeChangelog` task are `changelog` 
and `newChangelog`.
 
 == 4. Changelog Validation in Pull Requests
 
-The `validate-changelog` GitHub workflow automatically checks that:
-
-. **CHANGES.txt is not edited directly** - All changes must use the YAML 
fragment approach
-. **A changelog entry is added** - Code changes must include a corresponding 
YAML file in `changelog/unreleased/`
+The `validate-changelog` GitHub workflow checks that code changes come with a
+changelog entry, and that any entry added under `changelog/unreleased/` is
+structurally valid.
 
 Pull requests that only touch documentation, tests, build files or CI 
configuration
 are exempt and pass without an entry.  Subjectively, some minor changes don't 
need one either.

Reply via email to