---
title: "One-Package Change Runs Whole Monorepo CI | StarSling"
description: "Why a change to one package rebuilds the whole monorepo in CI, and the five causes to check in order."
url: https://starsling.dev/github-actions/problems/slow-monorepo-ci
canonicalUrl: https://starsling.dev/github-actions/problems/slow-monorepo-ci
---

# A one-package change runs the whole monorepo's CI

[GitHub Actions](https://starsling.dev/github-actions) / [Problems](https://starsling.dev/github-actions/problems) / A one-package change runs the whole monorepo's CI

- [How StarSling works](https://starsling.dev/)

Diagnosis mode: hybrid. Last updated: 2026-08-31

Most of a monorepo's CI cost comes from work the diff never touched: builds, tests, and matrix legs for packages nobody changed. Fix the scope of the run before you fix its speed, then check the trigger filters, the Turborepo cache, and how much setup and job structure the workflow duplicates.

## Table of contents

- [Symptoms](#symptoms)
- [How to diagnose it](#how-to-diagnose-it)
- [Likely causes](#likely-causes)
- [Hand it to an agent](#verify)
- [Related pages](#related-pages)
- [Related symptoms](#related-symptoms)
- [Sources](#sources)

<a id="symptoms"></a>

## Symptoms

In a GitHub Actions run on a monorepo, every job and step is defined once in the workflow YAML and runs for every push that matches the trigger, regardless of which package the diff actually touched. If the build or test step never narrows its target to the changed packages, changing one file in one package pays for the whole tree every time. That is the first thing to check, because it is the single biggest lever: scoping the dominant step to what changed turns a full-repo run into a partial one. The trigger itself is the second lever, since a workflow with no path filter fires on every push even when the change cannot affect it at all, like a docs edit. Third, if the repo uses Turborepo, a misconfigured cache defeats scoping from a different angle: tasks re-execute even when nothing downstream needed them to. Fourth, duplicated setup steps across jobs multiply the fixed cost of every run independent of scope. Fifth, jobs that could run in parallel but are chained sequentially stretch wall-clock time even when the total work is already correctly scoped. Work through these in order: scope, trigger, cache, setup, parallelism.

- A pull request that touches one package under packages/ or apps/ still runs the build and test steps for every other package in the workspace.
- The Actions tab shows the same job duration whether the diff is one line in one package or a change across ten packages.
- A README or docs-only edit in one package still triggers the full CI workflow, including jobs that build or test code.
- Turborepo's task summary shows tasks re-executing instead of restoring from cache even when the touched files did not change those tasks' inputs.
- Multiple jobs in the same workflow run near-identical checkout, dependency install, and build steps back to back.

<a id="how-to-diagnose-it"></a>

## How to diagnose it

[ci-speedup](https://starsling.dev/ci-speedup) carries the detection logic behind the causes below and opens the fix as a reviewable pull request. Install it with `npx skills add starslingdev/skills`, then run `/ci-speedup` in your repository.

To check by hand:

1. Open the Actions run for a PR that touched exactly one package, and note which jobs ran and how long the longest one took.
2. Check the `on:` trigger block of the workflow that ran: does it have a `paths` or `paths-ignore` filter, or did it fire unconditionally?
3. Find the step that dominates that job's duration, and read its `run:` command: does it target the whole repo, or only the changed package and its dependents?
4. If the repo uses Turborepo, open the job's log for a `turbo run` summary line; a `MISS` or `full` result on a task that should have been unaffected by the diff points at a cache problem rather than a scoping problem.
5. Scan the workflow file for jobs with duplicated checkout/install/build steps, and for `needs:` chains or `max-parallel: 1` linking jobs that have no real dependency on each other.
6. Match whichever finding you have (unscoped build, unfiltered trigger, cache miss, duplicated setup, or sequential jobs) to the corresponding cause above and follow its fix page.

<a id="likely-causes"></a>

## Likely causes

Ordered by how often each one turns out to be the answer. Confirm a cause with its check before you change anything.

### 1. The build or test step is not scoped to the changed packages

When the dominant build or test step always processes every package instead of only the ones the diff touched and their dependents, the job's runtime stops tracking the size of the change and starts tracking the size of the repo. This is the most common driver of a one-package change producing a full-monorepo run, and it compounds with every package added to the workspace over time.

Confirm it: Look at the `run:` command for the build or test step in the slow job. If it invokes `turbo run build test` (or an `nx`, Bazel, or Gradle equivalent) without a `--filter`/`affected`/`--base` argument tied to the PR's diff, this is the cause.

Fix: [Change-scoped builds](https://starsling.dev/best-practices/github-actions/build-only-affected) (`ci.build.change-scoped`, detection mode runtime)

### 2. The workflow has no path filter on its trigger

A workflow's `on.push` or `on.pull_request` block runs the entire workflow for every commit that matches its branch rules, with no awareness of which files changed, unless a `paths` or `paths-ignore` filter narrows it. Without that filter, a change anywhere in the repo, including docs or unrelated packages, starts every job the workflow defines.

Confirm it: Open the workflow file and check the `on:` block. `grep -n 'paths' .github/workflows/*.yml` across the expensive workflows; a workflow with more than a couple of jobs and no `paths` or `paths-ignore` key is unfiltered.

Fix: [Path filters](https://starsling.dev/best-practices/github-actions/path-filter-workflows) (`ci.trigger.path-filter`, detection mode static)

### 3. Turborepo caching is misconfigured, so scoped tasks still re-execute

Even a correctly filtered `turbo run` can behave like a full rebuild if the cache itself is broken: `TURBO_FORCE: true` ignores the cache outright, a `TURBO_CACHE: remote:r` job reads a remote cache no sibling job ever writes, a task missing `outputs` in turbo.json has nothing to restore, a task missing `inputs` hashes every git-tracked file in its package so unrelated edits invalidate it, and a rotating secret listed in `globalEnv` busts the hash for every package that reads it.

Confirm it: Run `grep -rn 'TURBO_FORCE\|TURBO_CACHE' .github/workflows/` and `jq '.tasks // .pipeline | to_entries[] | select(.value.outputs == null or .value.inputs == null) | .key' turbo.json`; any hit is a candidate cache break.

Fix: [Turborepo cache health](https://starsling.dev/github-actions/optimizations/optimize-turborepo) (`ci.cache.turborepo`, detection mode static)

### 4. Every job repeats the same checkout, install, and build steps

When each job in the workflow independently runs checkout, dependency install, and build instead of sharing that work through a composite action or an artifact handoff from one job, the fixed cost of setup is paid once per job rather than once per run. Scoping the build to changed packages does not reduce this cost, since it happens before scoping is even evaluated.

Confirm it: Compare the first few steps of each job in the workflow file; if `actions/checkout`, dependency install, and a build step appear identically in more than one job, that duplication is a separate cost from anything scope-related.

Fix: [Duplicated job setup](https://starsling.dev/github-actions/optimizations/avoid-duplicated-setup) (`ci.hygiene.duplicated-setup`, detection mode static)

### 5. Independent jobs are chained sequentially instead of running in parallel

Jobs with no real data dependency between them still add to wall-clock time when they are linked through `needs:`, a `workflow_run` chain, or a matrix with `max-parallel: 1`. Even a correctly scoped, correctly cached run can look slow in the Actions UI simply because independent work is queued one job after another rather than run at the same time.

Confirm it: Read the `needs:` keys and any `workflow_run` triggers across the workflow files, and `grep -rn 'max-parallel' .github/workflows/`; jobs with no genuine output dependency on each other that still wait in sequence are the cause.

Fix: [Independent jobs parallelized](https://starsling.dev/github-actions/optimizations/parallelize-independent-jobs) (`ci.parallel.independent-jobs`, detection mode static)

<a id="verify"></a>

## Hand it to an agent

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this diagnosis against your repository and report which cause it found:

Diagnose why a change to one package in this monorepo triggers a full-repo CI run. Work through these in order and stop at the first one that matches, reporting which cause you found before changing anything: (1) check whether the dominant build/test step in the slow workflow job scopes to changed packages, e.g. `turbo run build test --filter=...[origin/main]`, `nx affected`, or `--changed`/`--onlyChanged` on the test runner; (2) check the workflow's `on.push`/`on.pull_request` block for a `paths`/`paths-ignore` filter; (3) if Turborepo is used, check for `TURBO_FORCE: true`, `TURBO_CACHE: remote:r` with no writer, and tasks in turbo.json missing `outputs` or `inputs`; (4) check whether jobs duplicate checkout/install/build steps that could be a composite action or shared artifact; (5) check for `needs:` chains, `workflow_run` triggers, or `max-parallel: 1` serializing independent jobs. Report the single most likely cause, cite the exact file and line, and do not apply a fix until asked.

<a id="related-pages"></a>

## Related pages

- [Build and test only what changed in GitHub Actions](https://starsling.dev/best-practices/github-actions/build-only-affected)
- [Filter workflows with paths and paths-ignore in GitHub Actions](https://starsling.dev/best-practices/github-actions/path-filter-workflows)
- [Optimize Turborepo caching in GitHub Actions](https://starsling.dev/github-actions/optimizations/optimize-turborepo)
- [Share setup steps across GitHub Actions jobs](https://starsling.dev/github-actions/optimizations/avoid-duplicated-setup)
- [Run independent GitHub Actions jobs in parallel](https://starsling.dev/github-actions/optimizations/parallelize-independent-jobs)
- [Why is GitHub Actions CI so slow?](https://starsling.dev/github-actions-too-slow)

<a id="related-symptoms"></a>

## Related symptoms

Other symptoms on this site that share a likely cause with this one. If none of the causes above is yours, one of these is usually the page you wanted.

- [Why your test job is slower in CI than it is locally](https://starsling.dev/github-actions/problems/slow-tests)
- [npm install or npm ci taking a large slice of every run](https://starsling.dev/github-actions/problems/slow-npm-install)
- [pnpm install is slow in GitHub Actions](https://starsling.dev/github-actions/problems/slow-pnpm-install)

<a id="sources"></a>

## Sources

- [GitHub Actions: filtering workflow runs by paths](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#example-including-paths)
- [GitHub Actions: using a matrix for your jobs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs)
- [Turborepo: constructing CI](https://turborepo.com/docs/crafting-your-repository/constructing-ci#running-only-affected-tasks)
- [Turborepo: turbo.json configuration reference](https://turborepo.com/docs/reference/configuration#tasks)
