Skip to content

Instantly share code, notes, and snippets.

@gwpl
Created June 23, 2025 09:36
Show Gist options
  • Save gwpl/50c43210561b9f75ad26e7e79f33f04d to your computer and use it in GitHub Desktop.
Save gwpl/50c43210561b9f75ad26e7e79f33f04d to your computer and use it in GitHub Desktop.
Optimizing Github Actions Notes

Author: ChatGPT o4-mini-high 2025-06 https://chatgpt.com/share/6859173a-08ac-8007-9d1b-e076bd1bd7e3

Here are several strategies you can combine to avoid running your full suite on every single commit and to scope down Cypress (or any other heavy) tests only when they’re really needed:


1. Use Path-Based Triggers & Job-Level Filters

A. Workflow-level paths / paths-ignore

If your workflows live in .github/workflows/, you can tell GitHub “don’t even kick off this workflow unless files in frontend/… changed” (and vice-versa).

# .github/workflows/frontend.yml
on:
  pull_request:
    paths:
      - 'frontend/**'
      - 'cypress/**'
      - 'package.json'        # or other files whose changes really require front-end tests
# .github/workflows/backend.yml
on:
  pull_request:
    paths:
      - 'backend/**'
      - 'api/**'
      - 'requirements.txt'

Pros: super simple; GitHub does the filtering Cons: hard to share common jobs & you end up with many tiny workflow files


B. Job-level Conditional Runs via dorny/paths-filter

  1. Install

    uses: dorny/paths-filter@v2
    with:
      filters: |
        frontend:
          - 'frontend/**'
          - 'cypress/**'
        backend:
          - 'backend/**'
  2. Branch jobs on its outputs:

    name: CI
    on: [pull_request]
    
    jobs:
      filter:
        runs-on: ubuntu-latest
        outputs:
          fw: ${{ steps.filter.outputs.frontend }}
          bw: ${{ steps.filter.outputs.backend }}
        steps:
          - uses: actions/checkout@v3
          - id: filter
            uses: dorny/paths-filter@v2
            with:
              filters: |
                frontend:
                  - 'frontend/**'
                  - 'cypress/**'
                backend:
                  - 'backend/**'
    
      test-frontend:
        if: needs.filter.outputs.fw == 'true'
        needs: filter
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Install & Run Cypress
            run: |
              cd frontend
              npm ci
              npm run test:cypress
    
      test-backend:
        if: needs.filter.outputs.bw == 'true'
        needs: filter
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Install & Run Backend Tests
            run: |
              cd backend
              pip install -r requirements.txt
              pytest

Pros: single workflow, clear “only run what changed” logic Cons: adds one small job to calculate diff


2. Batch / Cancel-in-Progress

If you’re iterating quickly on a PR, you usually only care about the latest commit’s results:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

This will automatically cancel earlier runs on the same branch/PR as soon as a new commit arrives, so you won’t have duplicate builds piling up.


3. Manual (“▷ Run full tests”) & Scheduled Full Suites

  1. Manual trigger for your heaviest jobs:

    on:
      workflow_dispatch:    # “Run workflow” button in UI
      pull_request:
        types: [opened, reopened, synchronize]
  2. Scheduled nightly or on-merge full-suite:

    on:
      push:
        branches:
          - main         # run full suite once merged
      schedule:
        - cron: '0 2 * * *'   # every day at 02:00 UTC

4. “Skip CI” by Commit Message

Developers can prefix small commits with [skip ci] (or [ci skip]) in the commit message. GitHub will skip all workflows for that commit automatically.


5. Putting It All Together

Here’s a concise example combining path-filters, concurrency, manual triggers, and skip-ci:

name: PR CI

on:
  pull_request:
    types: [opened, reopened, synchronize]
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  filter:
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.filter.outputs.frontend }}
      backend:  ${{ steps.filter.outputs.backend }}
    steps:
      - uses: actions/checkout@v3
      - id: filter
        uses: dorny/paths-filter@v2
        with:
          filters: |
            frontend:
              - 'frontend/**'
              - 'cypress/**'
            backend:
              - 'backend/**'

  lint:
    needs: filter
    if: "!contains(github.event.head_commit.message, '[skip ci]')"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci && npm run lint

  test-backend:
    needs: [filter, lint]
    if: needs.filter.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          cd backend
          pip install -r requirements.txt
          pytest

  test-frontend:
    needs: [filter, lint]
    if: needs.filter.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          cd frontend
          npm ci
          npm run test:unit
      - name: Run Cypress (only on manual dispatch)
        if: github.event_name == 'workflow_dispatch'
        run: |
          npm run test:cypress
  • Lint & unit tests run whenever relevant files change
  • Cypress only on manual “Run workflow”
  • Skip CI honored if commit message contains [skip ci]
  • Concurrency cancels stale runs

Further Tips

  • Cache dependencies (actions/cache) so repeated runs don’t reinstall everything.
  • Split workflows by category (e.g. ci-lint.yml, ci-e2e.yml) if you want different cadence or permissions.
  • Consider putting really slow suites behind a “canary” label or an explicit comment trigger (e.g. /run-e2e).

With these patterns you’ll only spend runner-minutes where you really need them—and still keep full coverage available when you ask for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment