Skip to content
compiler.dev

What Is a Concurrency Group in GitHub Actions?

Last updated

A concurrency group is a named key in a GitHub Actions workflow or job that allows only one run with that key at a time. Newer runs wait, or, with cancel-in-progress: true, cancel the older run that is still going.

How it works

You add a concurrency block with a group string. GitHub evaluates the string for each run. If another run with the same group is in progress, the new run is held as pending. When cancel-in-progress is true, the old run is cancelled instead and the new one starts immediately.

Only one run can be pending per group. If a third run arrives while one runs and one waits, the waiting run is dropped in favour of the newest, unless you cancel in progress.

The group string usually combines the workflow name and a ref, so each branch or pull request has its own lane. Cancelling superseded runs on a busy pull request is one of the easiest ways to cut wasted CI minutes.

Example

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Here every pull request gets its own group and old runs are cancelled on a new push. Pushes to main get a group by ref, and are queued rather than cancelled, so every commit on main is tested.

It also works at job level, for example to serialise deploys:

jobs:
  deploy:
    concurrency:
      group: production-deploy
      cancel-in-progress: false
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Common mistakes

  • Cancelling deploys halfway. A cancelled deploy can leave infrastructure in a bad state. Use cancel-in-progress: false for deploys.
  • Group too broad. A fixed string like ci across all branches makes unrelated pull requests cancel each other.
  • Cancelling main. Cancelling runs on the default branch hides which commit broke the build.
  • Cancelling merge queue runs. Runs from the merge_group event should not be cancelled; see merge queue.
  • Forgetting the workflow name. Two different workflows with the same group share a lane and cancel one another.

To trim spend further, see reduce your GitHub Actions bill.

FAQ

What is the difference between workflow and job concurrency?

Workflow-level concurrency controls whole runs. Job-level concurrency controls a single job, useful for locking a shared resource such as a deployment target.

Does cancel-in-progress save money?

Yes. Superseded runs stop consuming minutes. The saving depends on how often you push to open pull requests.

Are cancelled runs billed?

Minutes used before cancellation are billed. Minutes after cancellation are not.