CI Optimization: Skip Linting for README Changes in GitHub Actions
In a high-scale microservices monorepo, triggering expensive linting or testing jobs for documentation-only changes (like README.md updates) results in wasted compute minutes and developer friction. As a Senior Cloud Architect, optimizing the CI/CD feedback loop is critical for maintaining developer velocity and reducing cloud costs.
This guide addresses how to implement path-based filtering to intelligently skip jobs based on the specific files modified within a pull request or push event.
The Problem Logic
Section titled “The Problem Logic”The goal is to prevent the linting job from executing if the diff contains only README.md files within the services/ directory, but to ensure it runs if any .py, .js, .go, or configuration files are touched within those same directories.
Example Redundant Run Log
Section titled “Example Redundant Run Log”When the filter is not configured, you see unnecessary resource consumption in the GitHub Actions runner:
# GitHub Actions Runner Log2023-10-27T10:15:22.441Z [INFO] - Starting Job: Linter2023-10-27T10:15:24.120Z [DEBUG] - Checking changed files...2023-10-27T10:15:25.500Z [DEBUG] - Files detected: services/auth-api/README.md2023-10-27T10:15:26.800Z [INFO] - Running 'npm run lint'...2023-10-27T10:16:45.120Z [INFO] - Job completed. Duration: 1m 22s.# Result: Successfully linted, but 82 seconds of runner time wasted on MD files.Diagnostic Checklist
Section titled “Diagnostic Checklist”Before applying the fix, verify your environment and repository structure:
- Directory Structure: Confirm if services are nested (e.g.,
services/api-gateway/,services/user-service/). - Trigger Type: Identify if you are triggering on
push,pull_request, or both. - Glob Pattern Accuracy: Ensure your glob patterns correctly distinguish between root-level documentation and service-level documentation.
- Workflow Granularity: Decide if you want to skip the entire workflow or just a specific job within the workflow.
The Fix
Section titled “The Fix”Method 1: Workflow-Level Filtering (Simplest)
Section titled “Method 1: Workflow-Level Filtering (Simplest)”If your entire workflow consists only of linting, use the native on.<event>.paths-ignore configuration. This prevents the workflow from even being queued.
name: Microservice-Linter
on: push: branches: - main paths: - 'services/**' # Trigger if anything in services changes paths-ignore: - 'services/**/README.md' # EXCEPT if it is only a README - 'docs/**' # Ignore root docs pull_request: paths: - 'services/**' paths-ignore: - 'services/**/README.md'
jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Linter run: | echo "Executing code quality checks..." # Your lint command hereMethod 2: Job-Level Filtering (For Complex Monorepos)
Section titled “Method 2: Job-Level Filtering (For Complex Monorepos)”In many cloud environments, you might have one workflow that builds an image and lints code. You want the lint job to skip on README.md changes, but perhaps you still want a documentation-sync job to run. For this, use the dorny/paths-filter action.
name: CI-Pipeline
on: pull_request: branches: [main]
jobs: changes: runs-on: ubuntu-latest outputs: code: ${{ steps.filter.outputs.code }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v2 id: filter with: filters: | code: - 'services/**' - '!services/**/README.md'
lint-service: needs: changes if: ${{ needs.changes.outputs.code == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Environment uses: actions/setup-node@v3 with: node-version: '18' - name: Install Dependencies run: npm ci - name: Lint run: npm run lintWhy this works
Section titled “Why this works”- Glob Negation: In Method 2, the
!symbol explicitly negates the pattern. This tells the runner: “Check all files in services, but subtract any README.md from that list.” - Short-Circuiting: By using the
ifconditional in Method 2, thelint-servicejob will show as “Skipped” in the GitHub UI, providing a clear visual cue to developers that the CI behaved as expected. - Environment Efficiency: This logic reduces the load on your GitHub-hosted or self-hosted runners, preventing queue backups during high-volume documentation updates.