Repository: pytorch/xla
Pull request #9072: Disable running slow CI for doc-only changes
Author: login: ghpvnist
Created: 2025-05-01T00:39:41Z | Merged: 2025-05-10T23:42:54Z

While getting #9065 reviewed, I noticed that we're running expensive (and long) CI even for no-op changes like removing trailing whitespaces from markdown files.

Note that this only skips CI when the entire PR only consists of markdown changes. Removing the CI for comments are more difficult. However, this is a step in the right direction to save some resources and enable merging PRs faster.

fixes #9073

Linked issue #9073: Disable running slow CI for doc-only changes
## 🚀 Skip running CI for markdown related changes

## Motivation

I noticed that we're running expensive (and long) CI even for no-op changes like removing trailing whitespaces from markdown files.

## Pitch

* Saves valuable TPU resources for trivial changes.
* Faster PR merge times for quick doc fixes.

Diff:
diff --git a/.github/workflows/_build_plugin.yml b/.github/workflows/_build_plugin.yml
index 2a4899394029..15a750acc629 100644
--- a/.github/workflows/_build_plugin.yml
+++ b/.github/workflows/_build_plugin.yml
@@ -16,6 +16,11 @@ on:
         type: number
         description: Timeout in minutes for the build job
         default: 120
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
     secrets:
       gcloud-service-key:
         required: true
@@ -32,23 +37,31 @@ jobs:
       BAZEL_REMOTE_CACHE: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
     steps:
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
           cuda: true
       - name: Build
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           cd pytorch/xla/infra/ansible
           ansible-playbook playbook.yaml -vvv -e "stage=build_plugin arch=amd64 accelerator=cuda cuda_compute_capabilities=5.2,7.5,8.6 src_root=${GITHUB_WORKSPACE} cache_suffix=-ci" --skip-tags=fetch_srcs,install_deps
       - name: Upload wheel
+        if: inputs.has_code_changes == 'true'
         uses: actions/upload-artifact@v4
         with:
           name: cuda-plugin
           path: /dist/*.whl
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/_build_torch_with_cuda.yml b/.github/workflows/_build_torch_with_cuda.yml
index ad8bf50632e4..8e5db19d5b79 100644
--- a/.github/workflows/_build_torch_with_cuda.yml
+++ b/.github/workflows/_build_torch_with_cuda.yml
@@ -20,6 +20,11 @@ on:
         type: number
         description: Timeout in minutes for the build job
         default: 120
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
 jobs:
   build:
     runs-on: ${{ inputs.runner }}
@@ -33,23 +38,31 @@ jobs:
       MAX_JOBS: 24
     steps:
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
           cuda: true
       - name: Build PyTorch with CUDA enabled
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           cd pytorch
           python setup.py bdist_wheel
       - name: Upload wheel
+        if: inputs.has_code_changes == 'true'
         uses: actions/upload-artifact@v4
         with:
           name: torch-with-cuda
           path: pytorch/dist/*.whl
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/_build_torch_xla.yml b/.github/workflows/_build_torch_xla.yml
index 2584c0cced82..7988a09ce237 100644
--- a/.github/workflows/_build_torch_xla.yml
+++ b/.github/workflows/_build_torch_xla.yml
@@ -20,6 +20,11 @@ on:
         type: number
         description: Timeout in minutes for the build job
         default: 120
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
     secrets:
       gcloud-service-key:
         required: true
@@ -39,27 +44,37 @@ jobs:
       # Need to check out local composite actions before using them
       # https://github.com/orgs/community/discussions/11771
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
       - name: Build
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           cd pytorch/xla/infra/ansible
           ansible-playbook playbook.yaml -vvv -e "stage=build arch=amd64 accelerator=tpu src_root=${GITHUB_WORKSPACE} bundle_libtpu=0 build_cpp_tests=1 git_versioned_xla_build=1 cache_suffix=-ci" --skip-tags=fetch_srcs,install_deps
       - name: Upload wheel
+        if: inputs.has_code_changes == 'true'
         uses: actions/upload-artifact@v4
         with:
           name: torch-xla-wheels
           path: /dist/*.whl
       - name: Upload CPP test binaries
+        if: inputs.has_code_changes == 'true'
         uses: actions/upload-artifact@v4
         with:
           name: cpp-test-bin
           path: /tmp/test/bin
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
+
diff --git a/.github/workflows/_check_code_changes.yml b/.github/workflows/_check_code_changes.yml
new file mode 100644
index 000000000000..8615efd09264
--- /dev/null
+++ b/.github/workflows/_check_code_changes.yml
@@ -0,0 +1,111 @@
+name: Check Code Changes
+
+on:
+  workflow_call:
+    inputs:
+      event_name:
+        required: true
+        type: string
+      # For pull_request, base_sha is github.event.pull_request.base.sha (target branch tip)
+      # For push, base_sha is github.event.before
+      base_sha:
+        required: true
+        type: string
+      # For pull_request, head_sha is github.event.pull_request.head.sha (PR branch tip)
+      # For push, head_sha is github.sha
+      head_sha:
+        required: true
+        type: string
+    outputs:
+      has_code_changes:
+        description: "True if non-markdown code files were changed or event is workflow_dispatch/schedule, false otherwise."
+        value: ${{ jobs.check_files.outputs.has_code_changes }}
+
+jobs:
+  check_files:
+    runs-on: ubuntu-24.04
+    outputs:
+      has_code_changes: ${{ steps.perform_check.outputs.has_code_changes }}
+    steps:
+      - name: Checkout code for diff (if needed)
+        # Checkout only if a diff is actually needed
+        if: inputs.event_name != 'workflow_dispatch' && inputs.event_name != 'schedule'
+        uses: actions/checkout@v4
+        with:
+          # Fetch all history for all branches and tags.
+          # This is necessary for `git diff A...B` (three-dot diff) to find the merge base
+          # and correctly diff PR changes against the point where it diverged.
+          # It's also needed for `git diff A B` if A and B are far apart.
+          fetch-depth: 0
+
+      - name: Perform file content check
+        id: perform_check
+        run: |
+          echo "Event Name: ${{ inputs.event_name }}"
+          echo "Base SHA input (for PR: target branch; for Push: before SHA): ${{ inputs.base_sha }}"
+          echo "Head SHA input (for PR: PR head; for Push: current SHA): ${{ inputs.head_sha }}"
+
+          # Handle workflow_dispatch and schedule events first
+          if [[ "${{ inputs.event_name }}" == "workflow_dispatch" || "${{ inputs.event_name }}" == "schedule" ]]; then
+            echo "Event is ${{ inputs.event_name }}. Assuming code changes or full run needed."
+            echo "has_code_changes=true" >> "$GITHUB_OUTPUT"
+            exit 0 # Exit early, no diff needed
+          fi
+
+          # Handle initial push (base SHA is all zeros)
+          # For an initial push, all files in the head_sha are considered "changed" (new).
+          if [[ "${{ inputs.base_sha }}" == "0000000000000000000000000000000000000000" ]]; then
+            echo "Initial push (base SHA is zeros). Assuming code changes."
+            # We can list all files in the current commit (inputs.head_sha) if needed,
+            # but for simplicity, just assuming code changes is often sufficient.
+            # To be precise, one could do: git ls-tree -r --name-only ${{ inputs.head_sha }} > changed_files.txt
+            # And then apply the markdown filter. For now, we'll assume changes.
+            echo "has_code_changes=true" >> "$GITHUB_OUTPUT"
+            exit 0
+          fi
+
+          # Handle cases where base and head are the same (e.g., re-run on a specific commit, or a push with no new commits)
+          # This can happen if a workflow is re-run, or if a branch is pushed without new commits (e.g., force push to same SHA).
+          if [[ "${{ inputs.base_sha }}" == "${{ inputs.head_sha }}" ]]; then
+            echo "Base SHA is the same as Head SHA. No file changes. Assuming no code changes for skipping purposes."
+            echo "has_code_changes=false" >> "$GITHUB_OUTPUT"
+            exit 0
+          fi
+
+          # Ensure SHAs are valid before attempting diff
+          # (git rev-parse --verify will exit with non-zero if SHA is not found)
+          git rev-parse --verify ${{ inputs.base_sha }}^{commit} >/dev/null 2>&1 || { echo "Error: Base SHA ${{ inputs.base_sha }} not found or invalid."; exit 1; }
+          git rev-parse --verify ${{ inputs.head_sha }}^{commit} >/dev/null 2>&1 || { echo "Error: Head SHA ${{ inputs.head_sha }} not found or invalid."; exit 1; }
+
+
+          # Determine the diff command based on the event type
+          if [[ "${{ inputs.event_name }}" == "pull_request" ]]; then
+            # For pull requests, use three-dot diff (A...B).
+            # This shows changes on the PR branch (inputs.head_sha)
+            # since it diverged from the target branch (inputs.base_sha).
+            # inputs.base_sha is github.event.pull_request.base.sha
+            # inputs.head_sha is github.event.pull_request.head.sha
+            echo "Pull Request: Diffing ${{ inputs.base_sha }}...${{ inputs.head_sha }}"
+            git diff --name-only --no-renames ${{ inputs.base_sha }}...${{ inputs.head_sha }} > changed_files.txt
+          else # For 'push' and potentially other events not explicitly handled above
+            # For pushes, use two-dot diff (A B).
+            # inputs.base_sha is github.event.before
+            # inputs.head_sha is github.sha
+            echo "Push or other event: Diffing ${{ inputs.base_sha }} ${{ inputs.head_sha }}"
+            git diff --name-only --no-renames ${{ inputs.base_sha }} ${{ inputs.head_sha }} > changed_files.txt
+          fi
+
+          echo "Changed files:"
+          cat changed_files.txt
+
+          if [ ! -s changed_files.txt ]; then # Check if changed_files.txt is empty
+            echo "No files changed in the diff."
+            echo "has_code_changes=false" >> "$GITHUB_OUTPUT"
+          elif grep -q -v -E '\.md$' changed_files.txt; then
+            echo "Non-markdown code changes detected."
+            echo "has_code_changes=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "Only markdown changes detected or no non-markdown changes found in diff."
+            echo "has_code_changes=false" >> "$GITHUB_OUTPUT"
+          fi
+        shell: bash
diff --git a/.github/workflows/_docs.yml b/.github/workflows/_docs.yml
index f6a8d1b0814f..0ca3fa33475f 100644
--- a/.github/workflows/_docs.yml
+++ b/.github/workflows/_docs.yml
@@ -11,6 +11,11 @@ on:
         type: string
         description: Runner type for the test
         default: linux.4xlarge
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
     secrets:
       torchxla-bot-token:
         required: true
@@ -24,42 +29,50 @@ jobs:
       BRANCH_NAME: ${{ github.ref_name }}
     steps:
     - name: Fetch wheels
+      if: inputs.has_code_changes == 'true'
       uses: actions/download-artifact@v4
       with:
         name: torch-xla-wheels
         path: /tmp/wheels/
     - name: Install wheels
+      if: inputs.has_code_changes == 'true'
       shell: bash
       run: |
         pip install /tmp/wheels/*.whl
     - name: Checkout PyTorch/XLA Repo
+      if: inputs.has_code_changes == 'true'
       uses: actions/checkout@v4
       with:
         path: pytorch/xla
     - name: Build docs
+      if: inputs.has_code_changes == 'true'
       shell: bash
       run: |
         cd pytorch/xla/docs
         pip install -r requirements.txt
         sphinx-build -b html source build
     - name: Checkout GitHub Pages
+      if: inputs.has_code_changes == 'true'
       uses: actions/checkout@v4
       with:
         path: gh-pages
         ref: gh-pages
         token: ${{ github.event_name == 'push' && secrets.torchxla-bot-token || github.token }}
     - name: Merge changes
+      if: inputs.has_code_changes == 'true'
       shell: bash
       run: |
         subdir=${{ env.BRANCH_NAME == 'master' && 'master' || format('{0}/{1}', 'release', env.BRANCH_NAME) }}
         mkdir -p gh-pages/$subdir
         cp -fR pytorch/xla/docs/build/* gh-pages/$subdir
     - name: Upload preview as artifact
+      if: inputs.has_code_changes == 'true'
       uses: actions/upload-artifact@v4
       with:
         name: github-pages
         path: pytorch/xla/docs/build/
     - name: Deploy
+      if: inputs.has_code_changes == 'true' && github.event_name == 'push'
       shell: bash
       run: |
         cd gh-pages
@@ -68,4 +81,7 @@ jobs:
         git add . -v
         git diff --cached --exit-code || git commit -m "Update doc from commit ${{ github.sha }}"
         git push origin gh-pages
-      if: github.event_name == 'push'
+    - name: Report no code changes
+      if: inputs.has_code_changes == 'false'
+      run: |
+        echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml
index bf974c2bd22b..2b1ed0cb7f95 100644
--- a/.github/workflows/_test.yml
+++ b/.github/workflows/_test.yml
@@ -32,7 +32,11 @@ on:
           required: true
           type: string
           description: torch-commit
-
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
     secrets:
       gcloud-service-key:
         required: true
@@ -42,7 +46,7 @@ jobs:
     runs-on: ${{ inputs.runner }}
     container:
       image: ${{ inputs.dev-image }}
-      options: "${{ inputs.install-cuda-plugin && '--gpus all' || '' }} --shm-size 16g"
+      options: "${{ inputs.install-cuda-plugin == true && '--gpus all' || '' }} --shm-size 16g"
     strategy:
       fail-fast: false
       matrix:
@@ -78,12 +82,14 @@ jobs:
       BAZEL_REMOTE_CACHE: 1
     steps:
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
@@ -91,38 +97,42 @@ jobs:
           wheels-artifact: torch-xla-wheels
           cuda-plugin-artifact: ${{ inputs.install-cuda-plugin && 'cuda-plugin' || null }}
       - name: Fetch CPP test binaries
+        if: inputs.has_code_changes == 'true' && matrix.run_cpp_tests
         uses: actions/download-artifact@v4
         with:
           name: cpp-test-bin
           path: /tmp/test/bin
-        if: ${{ matrix.run_cpp_tests }}
       # GitHub Actions doesn't preserve executable permissions
       # https://github.com/actions/download-artifact?tab=readme-ov-file#permission-loss
       - name: Set CPP test permissions
+        if: inputs.has_code_changes == 'true' && matrix.run_cpp_tests
         run: |
           chmod +x /tmp/test/bin/*
           ls -l /tmp/test/bin
-        if: ${{ matrix.run_cpp_tests }}
       - name: Check GPU
+        if: inputs.has_code_changes == 'true' && inputs.install-cuda-plugin
         run: nvidia-smi
-        if: ${{ inputs.install-cuda-plugin }}
       - name: Install test deps
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           # TODO: Add these in setup.py
           pip install fsspec
           pip install rich
       - name: Checkout PyTorch Repo
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           repository: pytorch/pytorch
           path: pytorch
           ref: ${{ inputs.torch-commit }}
       - name: Checkout PyTorch/XLA Repo
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           path: pytorch/xla
       - name: Extra CI deps
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           set -x
@@ -133,10 +143,11 @@ jobs:
             pip install -r pytorch/xla/benchmarks/requirements.txt
           fi
       - name: Test
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: pytorch/xla/.github/scripts/run_tests.sh pytorch/ pytorch/xla/ $USE_COVERAGE
       - name: Upload coverage results
-        if: ${{ inputs.collect-coverage }}
+        if: inputs.has_code_changes == 'true' && inputs.collect-coverage
         shell: bash
         env:
           CIRCLE_WORKFLOW_ID: ${{ github.run_id }}
@@ -180,3 +191,7 @@ jobs:
                 gsutil cp inc_metadata.json gs://ng3-metrics/ng3-pytorchxla-coverage/incremental/pytorchxla/${CIRCLE_WORKFLOW_ID}/metadata.json
               fi
             fi
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/_test_requiring_torch_cuda.yml b/.github/workflows/_test_requiring_torch_cuda.yml
index 72cd7e326088..2c579184e914 100644
--- a/.github/workflows/_test_requiring_torch_cuda.yml
+++ b/.github/workflows/_test_requiring_torch_cuda.yml
@@ -27,7 +27,11 @@ on:
         required: true
         type: string
         description: torch-commit
-
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
 jobs:
   test:
     container:
@@ -48,12 +52,14 @@ jobs:
       BAZEL_REMOTE_CACHE: 1
     steps:
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
@@ -62,8 +68,10 @@ jobs:
           cuda-plugin-artifact: cuda-plugin
           cuda-torch-artifact: torch-with-cuda
       - name: Check GPU
+        if: inputs.has_code_changes == 'true'
         run: nvidia-smi
       - name: Install wheels
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           pip install /tmp/wheels/*.whl
@@ -78,35 +86,42 @@ jobs:
           python -c "import torch; assert torch.cuda.is_available()"
           echo "CUDA is available for PyTorch."
       - name: Checkout PyTorch Repo
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           repository: pytorch/pytorch
           path: pytorch
           ref: ${{ inputs.torch-commit }}
       - name: Checkout PyTorch/XLA Repo
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           path: pytorch/xla
       - name: Extra CI deps
+        if: inputs.has_code_changes == 'true' && matrix.run_triton_tests
         shell: bash
         run: |
           set -x
           pip install -U --pre jax jaxlib "jax-cuda12-plugin[with_cuda]" jax-cuda12-pjrt -f https://storage.googleapis.com/jax-releases/jax_nightly_releases.html
-        if: ${{ matrix.run_triton_tests }}
       - name: Install Triton
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           cd pytorch
           make triton
       - name: Python Tests
+        if: inputs.has_code_changes == 'true' && matrix.run_python_tests
         shell: bash
         run: |
           set -xue
           PJRT_DEVICE=CUDA python pytorch/xla/test/test_operations.py -v
           PJRT_DEVICE=CUDA python pytorch/xla/test/dynamo/test_dynamo.py -v
-        if: ${{ matrix.run_python_tests }}
       - name: Triton Tests
+        if: inputs.has_code_changes == 'true' && matrix.run_triton_tests
         shell: bash
         run: |
           PJRT_DEVICE=CUDA TRITON_PTXAS_PATH=/usr/local/cuda-12.3/bin/ptxas python pytorch/xla/test/test_triton.py
-        if: ${{ matrix.run_triton_tests }}
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/_tpu_ci.yml b/.github/workflows/_tpu_ci.yml
index 652d6ca9e8d9..263ee7f8e68d 100644
--- a/.github/workflows/_tpu_ci.yml
+++ b/.github/workflows/_tpu_ci.yml
@@ -7,23 +7,31 @@ on:
         type: number
         description: Timeout in minutes for the job run
         default: 120
+      has_code_changes:
+        required: false
+        type: string
+        description: Whether to run full workflow or not
+        default: 'true'
 jobs:
   tpu-test:
     runs-on: v4-runner-set
     timeout-minutes: ${{ inputs.timeout-minutes }}
     steps:
       - name: Checkout actions
+        if: inputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             .github/workflows/setup
           path: .actions
       - name: Setup
+        if: inputs.has_code_changes == 'true'
         uses: ./.actions/.github/workflows/setup
         with:
           torch-commit: ${{ inputs.torch-commit }}
           wheels-artifact: torch-xla-wheels
       - name: Install test dependencies
+        if: inputs.has_code_changes == 'true'
         shell: bash
         run: |
           # TODO: Add these in setup.py
@@ -34,9 +42,14 @@ jobs:
           pip install torch_xla[tpu] -f https://storage.googleapis.com/libtpu-wheels/index.html -f https://storage.googleapis.com/libtpu-releases/index.html
           pip install --upgrade protobuf
       - name: Run Tests
+        if: inputs.has_code_changes == 'true'
         env:
           PJRT_DEVICE: TPU
           TPU_LOG_DIR: tpu_logs
         run: |
           cd pytorch/xla
           test/tpu/run_tests.sh
+      - name: Report no code changes
+        if: inputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 1c9e17dd8538..4c7b108c1e71 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -15,32 +15,47 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
 
   get-torch-commit:
+    needs: [check_code_changes]
     runs-on: ubuntu-24.04
     outputs:
       torch_commit: ${{ steps.commit.outputs.torch_commit }}
     steps:
-      - id: commit
-        name: Get latest torch commit
+      - name: Get latest torch commit
+        id: commit
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         run: |
           echo "torch_commit=$(git ls-remote https://github.com/pytorch/pytorch.git HEAD | awk '{print $1}')" >> "$GITHUB_OUTPUT"
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
 
   build-torch-xla:
     name: "Build PyTorch/XLA"
     uses: ./.github/workflows/_build_torch_xla.yml
-    needs: get-torch-commit
+    needs: [check_code_changes, get-torch-commit]
     with:
       dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_tpuvm
       torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
       timeout-minutes: 240
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
     secrets:
       gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
 
   build-torch-with-cuda:
     name: "Build PyTorch with CUDA"
     uses: ./.github/workflows/_build_torch_with_cuda.yml
-    needs: get-torch-commit
+    needs: [check_code_changes, get-torch-commit]
     with:
       # TODO: bump CUDA version to either 12.4 or 12.6 (supported by PyTorch).
       # Ref: https://github.com/pytorch/xla/issues/8700
@@ -49,31 +64,35 @@ jobs:
       # note that to build a torch wheel with CUDA enabled, we do not need a GPU runner.
       runner: linux.24xlarge
       timeout-minutes: 120
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
 
   build-cuda-plugin:
     name: "Build XLA CUDA plugin"
     uses: ./.github/workflows/_build_plugin.yml
+    needs: [check_code_changes]
     with:
       dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_cuda_12.3
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
     secrets:
       gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
 
   test-python-cpu:
     name: "CPU tests"
     uses: ./.github/workflows/_test.yml
-    needs: [build-torch-xla, get-torch-commit]
+    needs: [build-torch-xla, check_code_changes, get-torch-commit]
     with:
       dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_tpuvm
       timeout-minutes: 120
       collect-coverage: false
       torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
     secrets:
       gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
 
   test-cuda:
     name: "GPU tests"
     uses: ./.github/workflows/_test.yml
-    needs: [build-torch-xla, build-cuda-plugin, get-torch-commit]
+    needs: [build-torch-xla, build-cuda-plugin, check_code_changes, get-torch-commit]
     with:
       dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_cuda_12.3
       runner: linux.g4dn.12xlarge.nvidia.gpu
@@ -81,26 +100,29 @@ jobs:
       collect-coverage: false
       install-cuda-plugin: true
       torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
     secrets:
       gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
 
   test-cuda-with-pytorch-cuda-enabled:
     name: "GPU tests requiring torch CUDA"
     uses: ./.github/workflows/_test_requiring_torch_cuda.yml
-    needs: [build-torch-with-cuda, build-torch-xla, build-cuda-plugin, get-torch-commit]
+    needs: [build-torch-with-cuda, build-torch-xla, build-cuda-plugin, check_code_changes, get-torch-commit]
     with:
       dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_cuda_12.3
       runner: linux.8xlarge.nvidia.gpu
       timeout-minutes: 300
       collect-coverage: false
       torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
 
   test-tpu:
     name: "TPU tests"
     uses: ./.github/workflows/_tpu_ci.yml
-    needs: build-torch-xla
+    needs: [build-torch-xla, check_code_changes]
     with:
       timeout-minutes: 300
+      has_code_changes: ${{ needs.check_code_changes.outputs.has_code_changes }}
     if: github.event_name == 'push' || github.event_name == 'pull_request'
 
   push-docs:
diff --git a/.github/workflows/lintercheck.yml b/.github/workflows/lintercheck.yml
index e45454e475f3..27e3769ac99b 100644
--- a/.github/workflows/lintercheck.yml
+++ b/.github/workflows/lintercheck.yml
@@ -8,12 +8,23 @@ on:
       - r[0-9]+.[0-9]+
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
   linter_check:
     runs-on: ubuntu-24.04
+    needs: [check_code_changes]
     steps:
       - name: Checkout repo
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         uses: actions/checkout@v3
       - name: Setup Python
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         uses: actions/setup-python@v4
         with:
           python-version: '3.10'
@@ -21,7 +32,9 @@ jobs:
       - run: pip install yapf==0.40.2  # N.B.: keep in sync with `torchax/dev-requirements.txt`, `infra/ansible/config/pip.yaml`
 
       - name: Check no TORCH_PIN
-        if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
+        if: >
+          (github.event_name == 'push' && github.event.ref == 'refs/heads/master') &&
+          needs.check_code_changes.outputs.has_code_changes == 'true'
         shell: bash
         run: |
           TORCH_PIN=./.torch_pin
@@ -32,6 +45,7 @@ jobs:
             echo "No ${TORCH_PIN} found, safe to land..."
           fi
       - name: Run clang-format
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         shell: bash
         env:
           CLANG_FORMAT: clang-format-16
@@ -57,6 +71,7 @@ jobs:
             echo "PASSED C++ format"
           fi
       - name: Run yapf
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         shell: bash
         run: |
           git_status=$(git status --porcelain)
@@ -77,3 +92,7 @@ jobs:
           else
             echo "PASSED Python format"
           fi
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
diff --git a/.github/workflows/torchax.yml b/.github/workflows/torchax.yml
index b04e3c24c9f7..deb1cf165d2e 100644
--- a/.github/workflows/torchax.yml
+++ b/.github/workflows/torchax.yml
@@ -15,28 +15,41 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
   torchax-cpu:
     runs-on: ubuntu-24.04
+    needs: [check_code_changes]
     strategy:
       matrix:
         python-version: ['3.10']
     steps:
       - name: Checkout repo
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
             torchax
       - name: Setup Python
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         shell: bash
         working-directory: torchax
         run: |
           pip install -r test-requirements.txt
           pip install -e .[cpu]
       - name: Run tests
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         working-directory: torchax
         shell: bash
         run: |
@@ -59,3 +72,8 @@ jobs:
           pytest test/test_view.py
           pytest test/test_util.py
           XLA_FLAGS=--xla_force_host_platform_device_count=4 pytest -n 0 test_dist/
+          echo "Tests completed."
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."


Reviews:
user: login: tengyifei
body: Thanks! The logic looks sensible to me, but looks like the workflow file has a syntax error:

![screenshot-2025-04-30-23-59-53](https://github.com/user-attachments/assets/2c392fa3-cbb5-450a-aa37-0ad9dd5e7c3c)

state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T07:00:43Z
user: login: bhavya01
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T17:35:49Z
user: login: ghpvnist
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T22:19:12Z
user: login: tengyifei
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T22:28:48Z
user: login: tengyifei
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T02:27:11Z
user: login: ghpvnist
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T05:20:47Z
user: login: tengyifei
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-10T05:22:27Z

Review comments:
diff_hunk: @@ -15,99 +15,178 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
 
   get-torch-commit:
     runs-on: ubuntu-24.04
+    needs: [check_code_changes]
     outputs:
       torch_commit: ${{ steps.commit.outputs.torch_commit }}
     steps:
-      - id: commit
-        name: Get latest torch commit
+      - name: Get latest torch commit
+        id: commit
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         run: |
           echo "torch_commit=$(git ls-remote https://github.com/pytorch/pytorch.git HEAD | awk '{print $1}')" >> "$GITHUB_OUTPUT"
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
 
   build-torch-xla:
     name: "Build PyTorch/XLA"
     uses: ./.github/workflows/_build_torch_xla.yml
-    needs: get-torch-commit
-    with:
-      dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_tpuvm
-      torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
-      timeout-minutes: 240
-    secrets:
-      gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
+    needs: [check_code_changes, get-torch-commit]
+    steps:
path: .github/workflows/build_and_test.yml
user: login: bhavya01
body: Probably just add an if statement here checking for code changes instead of steps and add a separate common stage for skip build notification instead of adding it to every step. I understand that we might need finer control to skip individual steps but for this PR, this might be sufficient

```
skip-build-notification:
    name: "Skip Build Notification"
    runs-on: ubuntu-latest
    needs: check-code-changes # Depends on the change detection job
    if: needs.check-code-changes.outputs.has_changes != 'true'
    steps:
      - name: Echo no changes message
        run: echo "No code changes detected. Skipping PyTorch/XLA build."
```
created_at: 2025-05-01T17:35:00Z
author_association: COLLABORATOR
diff_hunk: @@ -15,99 +15,178 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
 
   get-torch-commit:
     runs-on: ubuntu-24.04
+    needs: [check_code_changes]
     outputs:
       torch_commit: ${{ steps.commit.outputs.torch_commit }}
     steps:
-      - id: commit
-        name: Get latest torch commit
+      - name: Get latest torch commit
+        id: commit
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         run: |
           echo "torch_commit=$(git ls-remote https://github.com/pytorch/pytorch.git HEAD | awk '{print $1}')" >> "$GITHUB_OUTPUT"
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
 
   build-torch-xla:
     name: "Build PyTorch/XLA"
     uses: ./.github/workflows/_build_torch_xla.yml
-    needs: get-torch-commit
-    with:
-      dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_tpuvm
-      torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
-      timeout-minutes: 240
-    secrets:
-      gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
+    needs: [check_code_changes, get-torch-commit]
+    steps:
path: .github/workflows/build_and_test.yml
user: login: ghpvnist
body: My understanding is that each of these jobs are required to run. Having the if statement will bypass running them leaving the CI in pending state.
created_at: 2025-05-01T22:19:12Z
author_association: COLLABORATOR
diff_hunk: @@ -15,99 +15,178 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
+      head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
 
   get-torch-commit:
     runs-on: ubuntu-24.04
+    needs: [check_code_changes]
     outputs:
       torch_commit: ${{ steps.commit.outputs.torch_commit }}
     steps:
-      - id: commit
-        name: Get latest torch commit
+      - name: Get latest torch commit
+        id: commit
+        if: needs.check_code_changes.outputs.has_code_changes == 'true'
         run: |
           echo "torch_commit=$(git ls-remote https://github.com/pytorch/pytorch.git HEAD | awk '{print $1}')" >> "$GITHUB_OUTPUT"
+      - name: Report no code changes
+        if: needs.check_code_changes.outputs.has_code_changes == 'false'
+        run: |
+          echo "No code changes were detected that require running the full test suite."
 
   build-torch-xla:
     name: "Build PyTorch/XLA"
     uses: ./.github/workflows/_build_torch_xla.yml
-    needs: get-torch-commit
-    with:
-      dev-image: us-central1-docker.pkg.dev/tpu-pytorch-releases/docker/development:3.10_tpuvm
-      torch-commit: ${{needs.get-torch-commit.outputs.torch_commit}}
-      timeout-minutes: 240
-    secrets:
-      gcloud-service-key: ${{ secrets.GCLOUD_SERVICE_KEY }}
+    needs: [check_code_changes, get-torch-commit]
+    steps:
path: .github/workflows/build_and_test.yml
user: login: tengyifei
body: yeah, it's a github quirk i think
created_at: 2025-05-01T22:28:48Z
author_association: COLLABORATOR
diff_hunk: @@ -15,32 +15,47 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
path: .github/workflows/build_and_test.yml
user: login: tengyifei
body: I think `github.event.pull_request.base.sha` isn't quite right. On the "Check Code Changes" step of this PR, I can see the following output:

![screenshot-2025-05-09-19-19-36](https://github.com/user-attachments/assets/ed80db24-da1b-4f9d-b888-b9ff52527015)

IOW, GitHub thinks that this PR changes some `.cpp` file, even if that is not the case.

I think that's because `base.sha` just means the SHA of the main branch at the time of running the check. Instead, we probably want the SHA of the commit right before the first commit of the PR in the author's source branch.
created_at: 2025-05-10T02:27:07Z
author_association: COLLABORATOR
diff_hunk: @@ -15,32 +15,47 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check_code_changes:
+    name: Check Code Changes
+    uses: ./.github/workflows/_check_code_changes.yml
+    with:
+      event_name: ${{ github.event_name }}
+      # For pull_request, use PR's base and head. For push, use event's before and sha.
+      base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
path: .github/workflows/build_and_test.yml
user: login: ghpvnist
body: Updated!
created_at: 2025-05-10T05:20:47Z
author_association: COLLABORATOR

PR comments:
user: login: tengyifei
created_at: 2025-05-01T00:46:43Z
body: Currently we require that the CPU and TPU tests must pass before a PR can be landed.

If these checks are skipped, will that prevent any markdown changes from landing, since GitHub checks would look like this:

![screenshot-2025-04-30-17-46-33](https://github.com/user-attachments/assets/f6df0b01-d7cb-46a5-a3d3-eabd1557bf56)

author_association: COLLABORATOR
user: login: ghpvnist
created_at: 2025-05-01T02:15:02Z
body: Okay I've done some testing on my fork. It should trigger the workflows when there are code changes. If PR only has markdown changes, it should return success early and skip running the actual tests.
author_association: COLLABORATOR
user: login: tengyifei
created_at: 2025-05-01T22:32:56Z
body: @ghpvnist still not works:

```
Invalid workflow file: .github/workflows/build_and_test.yml#L54
The workflow is not valid. .github/workflows/build_and_test.yml (Line: 54, Col: 9): Unexpected value 'secrets' .github/workflows/build_and_test.yml (Line: 44, Col: 5): Required property is missing: runs-on
```
author_association: COLLABORATOR
user: login: tengyifei
created_at: 2025-05-05T20:42:38Z
body: @ghpvnist could you try rebasing? looks like there are still test failures
author_association: COLLABORATOR
user: login: ghpvnist
created_at: 2025-05-05T21:01:47Z
body: I did rebase this ~2 hrs ago. @tengyifei Does `nvidia-smi: not found` or `docker: command not found` ring a bell?
author_association: COLLABORATOR
user: login: tengyifei
created_at: 2025-05-08T08:21:11Z
body: Unfortunately I'm not sure why there would be an nvidia-smi failure on CPU CI. 
NVIDIA-SMI is used to check GPU chips status. 
If we're running that on CPU CI, it's expected for it to fail. 
Maybe the yaml rules got mixed up somehow? 
@ghpvnist 
author_association: COLLABORATOR
user: login: tengyifei
created_at: 2025-05-10T02:27:19Z
body: Nice work!
author_association: COLLABORATOR