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

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.

hybrid · repo + run history
How StarSling works

AI agents find the cause

StarSling agents run this diagnosis against your workflows and run history, identify which cause is yours, and open the fix as a reviewable PR.

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.

How to diagnose it

  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.

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
  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
  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
  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
  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

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.

Prompt for your coding agent
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.

Go further

Find the cause, fix them all, or keep them fixed.

You have the prompt that works out which cause applies. Here is how much further you can take it, each step doing more for you than the last.

  1. Find the cause

    Copy the diagnosis prompt above

    Hand it to your coding agent to confirm which cause applies in your repo. It reports back what it found and changes nothing.

  2. Fix them all, once

    Install the ci-speedup skill

    One prompt audits your whole repo against all 73 ci-speedup patterns (these 15 plus 58 more) and hands your agent every fix at once. Open source, MIT, runs locally.

  3. Keep it fixed, forever

    Install the StarSling GitHub App

    Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.

Sources

1GitHub Actions: filtering workflow runs by paths (opens in new tab)

2GitHub Actions: using a matrix for your jobs (opens in new tab)

3Turborepo: constructing CI (opens in new tab)

4Turborepo: turbo.json configuration reference (opens in new tab)

Last updated 2026-08-31