When several developers open pull requests against the same branch, each change may pass its tests independently. The problem starts when those pull requests are merged close together. A change that passed testing against the original target branch may no longer be safe after another pull request modifies the same code.

GitHub Actions merge queues help manage this situation by testing proposed changes against the latest target branch and other changes waiting ahead of them in the queue. However, a poorly configured workflow can run expensive tests more often than necessary, consume CI minutes, and delay releases.

The goal is to keep merge queue validation reliable without repeating work that does not need to run again.

This article explains how GitHub Actions merge queues work, how to configure the required workflows, which tests should run in the queue, and how to reduce unnecessary CI execution without weakening branch protection.

What Is a GitHub Actions Merge Queue?

A merge queue controls how approved pull requests enter a protected branch.

Without a merge queue, two pull requests can both pass their checks against the same version of the target branch. If the first pull request changes a shared API, dependency, or configuration file, the second pull request may no longer work correctly after the first is merged.

A merge queue addresses this by preparing a temporary merge group that combines the proposed change with the target branch and, when applicable, changes ahead of it in the queue.

GitHub Actions then runs the required checks against that merge group. If the checks pass, GitHub can merge the change according to the repository's queue settings. If they fail, the affected change must be updated or removed from the queue, depending on the circumstances.

For example, imagine three pull requests:

  • Pull request A updates a shared data model.

  • Pull request B changes an API that uses that model.

  • Pull request C updates a user interface.

Each pull request may pass its own tests. However, B could fail when combined with A. A merge queue helps detect this integration problem before the changes enter the protected branch.

Why Merge Queues Can Increase CI Costs

A merge queue does not guarantee fewer test executions. It introduces another validation context that must be considered when planning CI.

A typical workflow may run tests when:

  1. A developer pushes a commit to a pull request.

  2. A reviewer requests changes and the developer updates the branch.

  3. A pull request becomes eligible for the merge queue.

  4. The queue creates or updates a merge group.

  5. The merge group changes because an earlier pull request is merged or removed.

Some of these executions are necessary. Others may be avoidable if the workflow repeats expensive checks that already produced trustworthy results for the exact same inputs.

The key distinction is between tests that validate an individual change and tests that validate the proposed integration.

Configure the Required Merge Queue Events

GitHub Actions workflows that provide required checks for a merge queue must handle the merge_group event.

A workflow that listens only for pull_request may work during normal pull request validation but fail to report the required check for a merge group. Depending on branch protection and queue configuration, the change may remain blocked.

A basic workflow can listen for both events:

name: CI Validation

on:
  pull_request:
    branches:
      - main

  merge_group:
    types:
      - checks_requested

permissions:
  contents: read

jobs:
  test:
    name: Run Required Tests
    runs-on: ubuntu-latest

    steps:
      - name: Check out source
        uses: actions/checkout@v4

      - name: Set up .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release --no-restore

      - name: Run tests
        run: dotnet test --configuration Release --no-build

This example uses .NET commands, but the event configuration also applies to repositories using other languages and build systems.

The workflow runs for pull requests targeting main and for merge groups requesting checks. The merge queue can then receive the result of the required test job.

The .NET version should match the version supported by your application. If the repository uses a global SDK configuration file, use that configuration rather than maintaining conflicting SDK versions in multiple places.

Keep Required Check Names Stable

Branch protection rules and rulesets can require particular status checks. These checks must be reported with the expected names and associated with the appropriate commit or merge group.

Avoid changing a required job's name casually. Renaming a job can leave branch protection waiting for a status check that the workflow no longer reports.

Also, make sure every required workflow has a suitable trigger. If a required workflow does not run for a merge group, the queue cannot rely on its normal pull request result as a substitute.

Separate Fast Checks From Expensive Integration Tests

One of the most effective ways to reduce CI cost is to divide validation into layers.

Not every check needs to run at every stage, but required merge queue checks must still establish that the proposed integration is safe.

A practical structure might include:

Validation layer

Examples

Typical execution strategy

Fast feedback

Formatting, linting, basic static checks

Run on pull request updates

Unit tests

Business logic and isolated components

Run on pull request updates and merge groups

Integration tests

Database, message broker, service integration

Run on merge groups when integration risk warrants it

End-to-end tests

Critical user journeys

Run on merge groups or through a carefully designed release gate

Long-running tests

Performance, load, or extended compatibility tests

Run on a defined schedule or release gate, with additional checks when risk requires them

This table is a planning guide, not a rule that every repository should follow. If an end-to-end test protects a critical requirement and must pass before merging, it belongs in the required merge validation strategy.

The important point is to avoid running every expensive test for every small edit when a smaller set of checks can provide appropriate early feedback.

Run Independent Jobs in Parallel

Jobs that do not depend on one another can run concurrently.

For example, a repository might run unit tests and static analysis as separate jobs. This does not necessarily reduce total compute usage, but it can reduce the time a developer waits for validation.

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - run: dotnet test tests/UnitTests/UnitTests.csproj

  static-analysis:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - run: dotnet format --verify-no-changes

Both jobs run independently. In a production workflow, make sure the static analysis command is supported by the repository's SDK and configuration, and include any required restore steps.

If a merge queue requires both jobs, both must report successful results for the relevant merge group.

Use Caching to Reduce Repeated Build Work

Caching dependencies and build inputs can reduce the amount of work performed by repeated CI runs. It does not eliminate the need to validate the merge group.

For .NET projects, NuGet packages are a common caching target. GitHub Actions' dependency cache can avoid downloading unchanged packages on every run.

- name: Set up .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '10.0.x'
    cache: true
    cache-dependency-path: '**/packages.lock.json'

This configuration is most useful when the repository maintains package lock files. If the project does not use them, configure the cache dependency path to match the files that actually determine dependency resolution, or follow the caching approach supported by your SDK and repository structure.

Caching works best when cache keys reflect relevant dependency inputs. If dependencies change, the workflow should use a cache appropriate to the new dependency set.

Do not treat a cached build output as proof that the current merge group has passed tests. A dependency cache speeds up setup; the test suite still needs to validate the code being proposed for merge.

Avoid Caching Untrusted Build Outputs

Build outputs can depend on source code, compiler options, environment variables, generated files, and external tools. Reusing outputs across different commits or trust boundaries can produce incorrect results or introduce security risks.

Prefer caching dependencies and other reproducible inputs over blindly reusing compiled binaries.

For repositories that accept contributions from forks, review cache access and cache-key design carefully. A workflow should not allow untrusted pull request code to influence cached content later consumed by a privileged release workflow.

Avoid Redundant Work With Artifacts

Sometimes an expensive operation produces an output that can be reused safely. For example, a workflow may generate a test report, a package, or a static analysis result.

Reuse is appropriate only when the output is valid for the exact inputs being evaluated.

Consider a build artifact produced for an individual pull request. The merge group may contain that pull request plus changes from the target branch or other queued pull requests. The artifact from the original pull request may not represent the final combined source.

Before reusing any output, determine whether its validity depends on:

  • The source commit or merge-group revision.

  • Dependency versions and lock files.

  • Compiler or runtime versions.

  • Build configuration and feature flags.

  • Operating system or architecture.

  • Generated source and build-time inputs.

If any relevant input changes, regenerate the output or verify that the reuse strategy remains correct.

Use Stable Artifact Identifiers

If a workflow publishes artifacts for later jobs, include a unique identifier tied to the run or revision.

For example:

- name: Upload test results
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: test-results-${{ github.run_id }}-${{ github.run_attempt }}
    path: test-results/
    retention-days: 14
    if-no-files-found: warn

The run identifier and attempt number help distinguish separate executions. The retention period is illustrative and should match the team's debugging and audit requirements.

This step preserves reports for investigation. It does not itself avoid repeated tests. To reduce repeated computation, the workflow must identify which outputs can be safely reused and verify that they correspond to the current inputs.

Use Concurrency Carefully

GitHub Actions supports concurrency controls that can prevent unnecessary overlapping runs. However, concurrency settings need special care in merge queue workflows.

For ordinary pull request validation, cancelling an older run after a new commit arrives may save resources because the older commit is no longer the one being reviewed.

A common configuration looks like this:

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

This expression is suitable for many pull request workflows, but it should not be copied blindly into every workflow. The merge queue uses merge-group references, and cancelling a required merge-group check can delay or disrupt queue progress.

For merge queue validation, choose concurrency keys that distinguish independent merge groups unless the workflow's behavior has been deliberately designed and tested otherwise.

Avoid using one broad concurrency group for all merge groups if that group causes one required validation run to cancel another. A queued change may be waiting for a check that never completes successfully because its run was cancelled.

The safest approach is to separate pull request and merge-group concurrency policies when their cancellation requirements differ.

Choose the Right Merge Queue Test Strategy

GitHub's merge queue settings influence how changes are grouped and how many changes are validated together. The correct settings depend on the repository's change rate, test duration, and tolerance for integration risk.

A repository with a small number of daily pull requests may benefit from a straightforward queue with comprehensive checks. A busy monorepo with long-running integration tests may need more careful test selection and queue configuration.

Review the following factors:

  • How often pull requests modify shared components.

  • How long required checks take.

  • Whether tests are deterministic.

  • Whether the queue frequently rebuilds groups.

  • How much compute capacity is available.

  • Which tests are essential to protect the target branch.

If tests are flaky, rerunning them may temporarily unblock the queue without addressing the underlying problem. Track flaky-test frequency and fix unreliable tests instead of building a workflow that repeatedly retries failures without clear limits.

Measure the Cost Before Optimizing

Collect baseline data before changing the workflow. Useful measurements include:

  • CI minutes consumed per merged pull request.

  • Average and high-percentile queue wait time.

  • Average duration of required merge-group checks.

  • Number of cancelled and repeated jobs.

  • Failure rate by test suite.

  • Cache hit rate.

  • Number of merge groups that require revalidation.

Compare these metrics before and after each change. A configuration that lowers compute usage but substantially increases queue delays may not be an improvement for the development team.

Common Mistakes to Avoid

Listening Only for Pull Request Events

A workflow that does not respond to the required merge-group event may leave the merge queue waiting for a status check. Add merge-group support to the workflows that provide required checks.

Reusing Tests From the Wrong Commit

A successful result for a pull request's original revision does not automatically prove that a later merge group passes. Reuse results only when the tested inputs are equivalent and that equivalence can be established reliably.

Cancelling Required Merge-Group Jobs

An overly broad concurrency group may cancel validation that the queue needs. Keep merge-group execution isolated where necessary and test cancellation behavior before enabling it in a protected production branch.

Skipping Tests to Save CI Minutes

Removing required integration tests without a risk-based replacement can allow incompatible changes into the target branch. Optimize setup time, caching, parallel execution, and test selection before weakening important validation requirements.

Ignoring Flaky Tests

Repeated retries can increase queue time and compute usage while hiding test instability. Track flaky tests separately and make the expected retry policy explicit.

Assuming Caching Eliminates Validation

Caching can speed up dependency restoration and other repeatable preparation steps. It cannot prove that the current combined source revision is correct.

Advantages and Limitations

Merge queues help teams integrate changes in a controlled order and detect conflicts that individual pull request tests may miss. They are particularly useful for busy repositories where multiple approved changes target the same protected branch.

They also introduce additional CI activity, configuration complexity, and potential queue delays. Expensive test suites and unstable checks can make those costs more noticeable.

The best results come from combining merge queues with reliable tests, appropriate caching, carefully scoped concurrency, and a clear distinction between pull request feedback and final integration validation.

Summary

GitHub Actions merge queues improve branch stability by validating proposed changes in the context in which they will be merged. Reducing unnecessary CI work requires careful workflow design rather than simply skipping checks.

Start by handling the merge_group event in every workflow that supplies required status checks. Separate fast feedback from expensive integration tests, cache dependencies, reuse artifacts only when their inputs are equivalent, and configure concurrency so that required merge-group checks are not cancelled accidentally.

Finally, measure CI cost and queue performance before and after each optimization. The objective is to reduce wasted computation while preserving reliable validation of the exact changes entering the protected branch.