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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0ef20170c build(ci): stop .claude-only changes from triggering full 
builds (#1846)
0ef20170c is described below

commit 0ef20170ce1f3be9d5e3e1e5a4b770ef40a5c6d6
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Aug 14 12:48:33 2026 +0200

    build(ci): stop .claude-only changes from triggering full builds (#1846)
    
    Editing an agent skill rebuilt the whole project on GitHub Actions and
    Jenkins. No code changes, so every one of those runs was wasted.
    
    GitHub Actions, non-required workflows (codeql, owasp, sonar): plain
    paths-ignore on both push and pull_request. Nothing they report is
    required in .asf.yaml, so a run that never happens blocks nothing.
    
    GitHub Actions, maven.yml: paths-ignore on push only. It is deliberately
    NOT applied to pull_request, because "Build and Test (JDK 17)" is a
    required check and GitHub documents that a workflow skipped by path
    filtering never reports - the check stays Pending and the pull request
    can never be merged. Instead a small `changes` job inspects the PR's
    file list and the build job is skipped by condition. A job skipped that
    way does report, as "skipped", and required checks accept "successful,
    skipped, or neutral".
    
    Jenkins polls SCM, so the trigger cannot be filtered; the two JDK stages
    are guarded instead. Detection fails open - no previous successful
    commit, an unreachable commit, or any git error reports true and the
    build runs exactly as before.
    
    The filter tests for a non-empty list of files outside .claude/ rather
    than using `grep -qv`: the local ugrep 7.5.0 returns 1 from `-qv` on
    input where `-cv` counts 1 and `-v` prints the line, which silently
    inverts the decision. Testing emptiness behaves the same everywhere.
    Exercised against six inputs, including the mixed .claude/ + code case
    that must still build, and .claudefoo/ which must not be treated as
    .claude/.
    
    Only the 7.x line is changed here; support/struts-6-x-x needs the same
    edit on its own branch.
    
    Co-authored-by: Claude Opus 5 <[email protected]>
---
 .github/workflows/codeql.yml |  6 ++++++
 .github/workflows/maven.yml  | 43 +++++++++++++++++++++++++++++++++++++++++++
 .github/workflows/owasp.yml  |  6 ++++++
 .github/workflows/sonar.yml  |  6 ++++++
 Jenkinsfile                  | 31 +++++++++++++++++++++++++++++++
 5 files changed, 92 insertions(+)

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 572f99f08..f3cf28201 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -21,7 +21,13 @@ on:
       - 'main'
       - 'release/*'
       - 'support/*'
+    paths-ignore:
+      - '.claude/**'
+  # Safe to filter by path here: no check from this workflow is required in
+  # .asf.yaml, so a run that never happens blocks nothing.
   pull_request:
+    paths-ignore:
+      - '.claude/**'
 
 permissions:
   # Needed to upload the results to code-scanning dashboard.
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 754dc98e3..6d1e86112 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -16,6 +16,11 @@
 name: Java Maven
 
 on:
+  # Deliberately NOT filtered by path. "Build and Test (JDK 17)" is a required
+  # status check in .asf.yaml, and a workflow skipped by path filtering never
+  # reports its checks - they stay Pending and the pull request can never be
+  # merged. The build job is skipped by condition instead (see `changes` 
below),
+  # which does report, as "skipped", and satisfies the requirement.
   pull_request:
   push:
     branches:
@@ -23,6 +28,8 @@ on:
       - 'develop'
       - 'release/*'
       - 'support/*'
+    paths-ignore:
+      - '.claude/**'
   workflow_dispatch:
   workflow_call:
 
@@ -33,8 +40,44 @@ env:
   LANG: en_US.utf8
 
 jobs:
+  changes:
+    name: Detect changes outside .claude
+    runs-on: ubuntu-latest
+    outputs:
+      code: ${{ steps.filter.outputs.code }}
+    steps:
+      - name: Check which paths the pull request touches
+        id: filter
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          set -eu
+          if [ "${{ github.event_name }}" != "pull_request" ]; then
+            echo "Not a pull request - building."
+            echo "code=true" >> "$GITHUB_OUTPUT"
+            exit 0
+          fi
+          files=$(gh api --paginate \
+            "repos/${{ github.repository }}/pulls/${{ github.event.number 
}}/files" \
+            --jq '.[].filename')
+          echo "Changed files:"
+          printf '%s\n' "$files"
+          # Anything outside .claude/ means a real build is needed; an empty
+          # diff, or one confined to .claude/, does not. Tested by emptiness
+          # rather than with `grep -qv`, whose exit status is not reliable
+          # across grep implementations.
+          outside=$(printf '%s\n' "$files" | grep -vE '^(\.claude/|$)' || true)
+          if [ -n "$outside" ]; then
+            echo "code=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "Only .claude/ changed - skipping the build."
+            echo "code=false" >> "$GITHUB_OUTPUT"
+          fi
+
   build:
     name: Build and Test (JDK ${{ matrix.java }})${{ matrix.profile == 
'-Pjakartaee11' && ' (Jakarta EE 11 + Spring 7)' || matrix.profile }}
+    needs: changes
+    if: needs.changes.outputs.code == 'true'
     runs-on: ubuntu-latest
     strategy:
       fail-fast: false
diff --git a/.github/workflows/owasp.yml b/.github/workflows/owasp.yml
index 661b09187..7b6d0523a 100644
--- a/.github/workflows/owasp.yml
+++ b/.github/workflows/owasp.yml
@@ -16,13 +16,19 @@
 name: OWASP checkup
 
 on:
+  # Safe to filter by path here: no check from this workflow is required in
+  # .asf.yaml, so a run that never happens blocks nothing.
   pull_request:
+    paths-ignore:
+      - '.claude/**'
   push:
     branches:
       - 'main'
       - 'develop'
       - 'release/*'
       - 'support/*'
+    paths-ignore:
+      - '.claude/**'
   workflow_dispatch: #Allow manual triggers
 
 permissions: read-all
diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index bf33a5520..becd7c789 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -16,10 +16,16 @@
 name: SonarCloud
 
 on:
+  # Safe to filter by path here: no check from this workflow is required in
+  # .asf.yaml, so a run that never happens blocks nothing.
   pull_request:
+    paths-ignore:
+      - '.claude/**'
   push:
     branches:
       - 'main'
+    paths-ignore:
+      - '.claude/**'
 
 permissions: read-all
 
diff --git a/Jenkinsfile b/Jenkinsfile
index 1fce32d7c..105625d5d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -41,9 +41,37 @@ pipeline {
             cleanWs deleteDirs: true, patterns: [[pattern: '**/target/**', 
type: 'INCLUDE']]
           }
         }
+        stage('Detect changes') {
+          steps {
+            script {
+              // Skip the build when a push only touched .claude/ - agent
+              // instructions, not code. Fails open: anything unexpected (no
+              // previous successful build, an unreachable commit, a git error)
+              // reports true and the build runs as before.
+              env.CODE_CHANGED = sh(returnStdout: true, script: '''
+                set -u
+                base="${GIT_PREVIOUS_SUCCESSFUL_COMMIT:-}"
+                if [ -z "$base" ] || ! git cat-file -e "${base}^{commit}" 
2>/dev/null; then
+                  echo true
+                  exit 0
+                fi
+                outside=$(git diff --name-only "$base" HEAD | grep -vE 
'^(\\.claude/|$)' || true)
+                if [ -n "$outside" ]; then
+                  echo true
+                else
+                  echo false
+                fi
+              ''').trim()
+              echo "Changes outside .claude/: ${env.CODE_CHANGED}"
+            }
+          }
+        }
       }
     }
     stage('JDK 21') {
+      when {
+        expression { env.CODE_CHANGED != 'false' }
+      }
       agent {
         label 'ubuntu'
       }
@@ -74,6 +102,9 @@ pipeline {
       }
     }
     stage('JDK 17') {
+      when {
+        expression { env.CODE_CHANGED != 'false' }
+      }
       agent {
         label 'ubuntu'
       }

Reply via email to