Skip to content

Run Only Tasks Affected by a PR

Remote caching speeds up your CI, but you can go even faster by skipping unnecessary tasks altogether.

Most pull requests change a few files, so CI should only run the tasks those files affect. nx affected selects and runs only the tasks that need to run for the given changes.

For example, say a web app depends on a ui library, and your pull request only updates the unit test file button.spec.ts in ui. nx affected runs ui:test and skips ui:build, since spec files aren't an input to the production build. It skips web:build and web:test too, because the spec file for ui isn't an input to either.

Changing button.spec.ts in ui runs ui:test and skips ui:build, web:build, and web:test

Run nx affected with the targets you want, particularly on CI:

Terminal window
nx affected -t <targets>

For the web and ui example, run the affected build and test tasks:

Terminal window
nx affected -t build test

Nx then:

  1. Uses Git to find the files your branch changed, for example libs/ui/src/button.spec.ts.
  2. Selects each build and test task whose inputs include a changed file.
  3. Selects the tasks that read the outputs of a selected task, like a web:build that reads the ui build output.
  4. Runs the selected tasks and the tasks they depend on, restoring unchanged dependencies from the cache.

Only ui:test reads the spec file. In this nx.json, production is a named input that leaves out spec files, and ^ reads an input from a project's dependencies:

nx.json
{
"namedInputs": {
"production": ["{projectRoot}/**/*", "!{projectRoot}/**/*.spec.ts"]
},
"targetDefaults": {
"build": {
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["{projectRoot}/**/*", "^production"]
}
}
}

To see the affected tasks in the task graph without running them, pass --graph:

Terminal window
nx affected -t build test --graph

The affected command takes a base and head commit. The default base is your main branch, and the default head is your current file system. This is generally what you want when developing locally, but in CI, you need to customize these values.

Terminal window
nx affected -t build --base=origin/main --head=$PR_BRANCH_NAME # where PR_BRANCH_NAME is defined by your CI system
nx affected -t build --base=origin/main~1 --head=origin/main # rerun what is affected by the last commit in main

You can also set the base and head SHAs as environment variables:

Terminal window
NX_BASE=origin/main~1
NX_HEAD=origin/main

The recommended approach is to set the base SHA to the latest successful commit on the main branch. This ensures that all changes since the last successful CI run are accounted for.

The nrwl/nx-set-shas action uses the GitHub API to find the last successful workflow run on main and sets NX_BASE and NX_HEAD, so nx affected needs no extra flags.

.github/workflows/ci.yml
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
filter: tree:0
- run: npm ci
- uses: nrwl/nx-set-shas@v5
- run: npx nx affected -t lint test build

fetch-depth: 0 is required. Without the full Git history, there is no base commit to compare against.

For other CI providers (Jenkins, Azure Pipelines, Bitbucket), see our guides to get the last successful CI run for your CI provider.

Use the --explain flag to show why tasks are affected. Nx prints the explanation, then exits without running anything. Say your branch changes libs/ui/src/index.ts instead of the spec file:

Terminal window
nx affected -t build --explain
NX 2 out of 2 build tasks are affected
Changing 1 file touches 2 build tasks. Pass --verbose to list each with its reasons.
libs/ui/src/index.ts:
- ui:build
- web:build

--explain explains task selection, so it needs targets passed with -t. Without them, or with affected.legacy set, it stops with an error.

Pass --explain=stdout for the data as JSON, or --explain=reasons.json to write it to a file. nx show projects --affected -t build --explain works the same way.

Best paired with remote caching and distribution

Section titled “Best paired with remote caching and distribution”

nx affected can still select a lot of work:

  • A change to a project most of the workspace depends on, or to an input every task shares, selects most tasks.
  • Every push to a pull request is compared with the same base commit on main, so each push selects the same tasks again.

Remote caching replays the selected tasks whose inputs didn't change since an earlier run, so a later push only runs what its new commits affect. With Ultracache, Nx selects tasks from the files each task read in CI instead of its declared inputs, so unused files don't select tasks and reads missing from inputs aren't overlooked. Distributed task execution spreads the tasks that do run across machines.

Nx provides two methods to exclude glob patterns (files and folders) from nx affected -t <targets>:

  • Glob patterns defined in your .gitignore file are ignored.
  • Glob patterns defined in an optional .nxignore file are ignored.

By default, a change to your package manager's lock file affects every task. To limit it to the tasks of projects whose resolved dependencies changed, set projectsAffectedByDependencyUpdates to "auto" in nx.json.

If you aren't using Git, you can pass --files to any affected command to indicate what files have been changed.

Nx 23 and earlier use project-based affected

Section titled “Nx 23 and earlier use project-based affected”

Nx 23 and earlier use project-based affected: a change to a project runs every task of that project and of every project that depends on it. Running nx migrate to Nx 24 keeps that behavior in existing workspaces by setting affected.legacy to true in nx.json:

nx.json
{
"affected": {
"legacy": true
}
}

Remove the setting, or set it to false, to use task-based affected. NX_LEGACY_AFFECTED overrides it for one invocation: true uses project-based affected and false uses task-based. Set it for the whole CI job, so nx show projects --affected -t test and nx affected -t test in the same script see the same affected set. nx graph --affected always shows projects.