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

lukaszlenart pushed a commit to branch build/skip-ci-for-claude-only-changes
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 21bd2f7bbf01533e152396cebc902605c4333e76
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Aug 14 12:28:37 2026 +0200

    build(ci): stop .claude-only changes from triggering full builds
    
    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 5f0214d01..6e7cf4d8b 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