---
title: Job Selection
description: Skip the CI jobs a pull request does not need, using the scopes declared in your Mergify configuration.
---

Job Selection decides, while CI runs, which jobs a pull request actually needs. In a monorepo most
changes touch one corner of the codebase, yet a plain pipeline runs every test suite on every pull
request. With Job Selection, a pull request that only changes the frontend runs the frontend jobs
and skips the API and docs suites, so you stop paying for CI work that cannot fail because of the
change.

It is built on [scopes](/merge-queue/scopes): named areas of your repository, declared once in
`.mergify.yml`. The same declaration serves two consumers. The [merge queue](/merge-queue) reads
scopes to batch related pull requests together, and Job Selection reads them in CI to skip
unaffected jobs. Each works without the other, so you can adopt Job Selection without the merge
queue, or the reverse.

```dot class="queue" title="One scopes block feeds Job Selection in CI and batching in the merge queue"
digraph {
  Config [label="scopes\n(.mergify.yml)", class="config"];
  Detect [label="Detect scopes\n(CI job)", class="pending"];
  Jobs [label="Affected jobs run,\nthe rest are skipped", class="pending"];
  Queue [label="Merge queue\nbatches by scope", class="mergify"];

  Config -> Detect [label="Job Selection"];
  Detect -> Jobs;
  Config -> Queue [label="Merge Queue Scopes"];
}
```

## How it works

1. **You declare scopes** as file patterns in `.mergify.yml`.

2. **A detection job runs first in your pipeline.** It matches the files the pull request changes
   against your patterns and reports each scope as `true` or `false`. The matching happens in the
   CI job itself, from your configuration and the pull request diff.

3. **Each job checks its scope** and runs only when the pull request touches it.

4. **One status sums up the jobs that ran**: a gate job on GitHub Actions, the build status on
   Buildkite. The check you require on your branch then passes or fails correctly even when most
   jobs were skipped.

## Declare your scopes

Job Selection reads a `files` source: each scope name maps to the glob patterns that define it.

```yaml
scopes:
  source:
    files:
      frontend:
        include:
          - apps/web/**/*
      api:
        include:
          - services/api/**/*
      docs:
        include:
          - docs/**/*
```

[File-pattern scopes](/merge-queue/scopes/file-patterns) covers the `include` and `exclude`
syntax, and the [scopes reference](/merge-queue/scopes#configuration-schema) lists every key of
the `scopes` block.

## Set it up in your CI

The wiring depends on your CI provider, so each integration page carries its own walkthrough:

- **[GitHub Actions](/integrations/gha#job-selection)**: a `detect-scopes` job runs the
  [`gha-mergify-ci`](https://github.com/Mergifyio/gha-mergify-ci) action, each job gates on its
  scope with an `if:` condition, and a `ci-gate` job publishes the final status.

- **[Buildkite](/integrations/buildkite#job-selection)**: a step runs the
  [`mergify-ci` plugin](https://github.com/Mergifyio/mergify-ci-buildkite-plugin), and a dynamic
  pipeline uploads only the steps whose scope matched.

Scopes themselves are CI-agnostic. For another provider, see
[other build tools](/merge-queue/scopes/others) or [let us know](mailto:support@mergify.com).

## Keep required checks meaningful

Skipping jobs changes what a required check tells you. On GitHub Actions, a job skipped by its
`if:` condition counts as passing, even when it was skipped because a job it depends on failed.
Require a single gate job instead: it waits for the jobs that ran and reports their combined
result. Keep the detection job among the jobs it waits for, otherwise a failed detection skips
every test job and the gate still passes. The
[GitHub Actions walkthrough](/integrations/gha#protecting-the-branch-with-ci-gate) shows this with
`ci-gate`.

On Buildkite, a step the pipeline never uploads reports no status at all, so require the build's
own status: it covers every step the dynamic pipeline uploaded, and a failed detection step fails
the build.

## Run jobs only in the merge queue

Detection also reports a special scope, `merge-queue`, that is `true` only on the draft pull
requests the merge queue creates. Gate expensive suites on it, such as end-to-end or integration
tests, to run them once before merging instead of on every push to a pull request. The name comes
from [`scopes.merge_queue_scope`](/merge-queue/scopes#top-level-keys). See
[the merge queue scope](/merge-queue/scopes/file-patterns#the-merge-queue-scope) for an example.

## Scopes from a build tool

If your scopes come from a build tool such as [Nx](/merge-queue/scopes/nx),
[Bazel](/merge-queue/scopes/bazel), [Pants](/merge-queue/scopes/pants) or
[Turborepo](/merge-queue/scopes/turborepo), your configuration uses a `manual` source and there are
no file patterns to match. On a pull request, the detection job refuses that source: your build
tool already knows which targets a pull request affects, so let it decide which jobs run, and
upload the scopes it computed so the merge queue can batch on them. Each build tool guide shows how
to compute and upload them.
