Concurrency Control & Path Filters

Cancel superseded runs and skip irrelevant ones so CI is fast, cheap, and never races itself.

Two settings quietly save enormous amounts of CI time and money: concurrency stops redundant runs from piling up, and path filters stop workflows firing when nothing they care about changed.

Concurrency

A concurrency group ensures only one run per group proceeds at a time. Set cancel-in-progress: true and a fresh push to a PR instantly cancels the now-stale run for that branch β€” no more three builds racing for the same commit.

  • Key the group by branch/PR: group: ci-${{ github.ref }}.
  • For deploys, do the opposite: keep cancel-in-progress: false so an in-flight production deploy is never killed mid-way.

Path filters

Under on.push / on.pull_request, paths and paths-ignore limit a workflow to relevant changes β€” don't run the full test suite for a README typo. For required status checks, prefer paths at the job level or a path filter action, since a skipped required check can block merges.

Example

Example Β· yaml
name: CI
on:
  pull_request:
    paths:
      - "src/**"
      - "package.json"
    paths-ignore:
      - "**/*.md"

# One live run per branch; a new push cancels the stale one
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

When to use it

  • A team adds concurrency groups so only one deployment workflow runs at a time per branch, cancelling queued runs when a newer push arrives.
  • A monorepo uses path filters so a push that changes only docs/ does not trigger the expensive build-and-test workflow.
  • A developer uses cancel-in-progress: true so a second push to a PR branch immediately cancels the still-running CI from the first push.

More examples

Cancel in-progress runs per branch

Groups concurrent runs by workflow name and ref; when a new push arrives on the same branch, the in-progress run is cancelled immediately.

Example Β· yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
  pull_request:

Path filters to skip unchanged services

The workflow only triggers when files under services/api/ or the shared package change, skipping runs for unrelated changes elsewhere in the monorepo.

Example Β· yaml
on:
  push:
    paths:
      - 'services/api/**'
      - 'packages/shared/**'
  pull_request:
    paths:
      - 'services/api/**'
      - 'packages/shared/**'

jobs:
  test-api:
    runs-on: ubuntu-latest
    steps:
      - run: npm test --workspace=services/api

Protect deployments with concurrency

Setting cancel-in-progress: false queues deployments instead of cancelling them, ensuring every merge to main is eventually deployed in order.

Example Β· yaml
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    concurrency:
      group: production-deploy
      cancel-in-progress: false  # queue, don't cancel
    environment: production
    steps:
      - run: ./scripts/deploy.sh

Discussion

  • Be the first to comment on this lesson.
Concurrency Control & Path Filters β€” GitHub | SoundsCode