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.
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
Open the Actions run for a PR that touched exactly one package, and note which jobs ran and how long the longest one took.
Check the
on:trigger block of the workflow that ran: does it have apathsorpaths-ignorefilter, or did it fire unconditionally?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?If the repo uses Turborepo, open the job's log for a
turbo runsummary line; aMISSorfullresult on a task that should have been unaffected by the diff points at a cache problem rather than a scoping problem.Scan the workflow file for jobs with duplicated checkout/install/build steps, and for
needs:chains ormax-parallel: 1linking jobs that have no real dependency on each other.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.
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.
Fix: Change-scoped buildsConfirm it
Look at the
run:command for the build or test step in the slow job. If it invokesturbo run build test(or annx, Bazel, or Gradle equivalent) without a--filter/affected/--baseargument tied to the PR's diff, this is the cause.The workflow has no path filter on its trigger
A workflow's
on.pushoron.pull_requestblock runs the entire workflow for every commit that matches its branch rules, with no awareness of which files changed, unless apathsorpaths-ignorefilter narrows it. Without that filter, a change anywhere in the repo, including docs or unrelated packages, starts every job the workflow defines.Fix: Path filtersConfirm it
Open the workflow file and check the
on:block.grep -n 'paths' .github/workflows/*.ymlacross the expensive workflows; a workflow with more than a couple of jobs and nopathsorpaths-ignorekey is unfiltered.Turborepo caching is misconfigured, so scoped tasks still re-execute
Even a correctly filtered
turbo runcan behave like a full rebuild if the cache itself is broken:TURBO_FORCE: trueignores the cache outright, aTURBO_CACHE: remote:rjob reads a remote cache no sibling job ever writes, a task missingoutputsin turbo.json has nothing to restore, a task missinginputshashes every git-tracked file in its package so unrelated edits invalidate it, and a rotating secret listed inglobalEnvbusts the hash for every package that reads it.Fix: Turborepo cache healthConfirm it
Run
grep -rn 'TURBO_FORCE\|TURBO_CACHE' .github/workflows/andjq '.tasks // .pipeline | to_entries[] | select(.value.outputs == null or .value.inputs == null) | .key' turbo.json; any hit is a candidate cache break.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.
Fix: Duplicated job setupConfirm 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.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:, aworkflow_runchain, or a matrix withmax-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.Fix: Independent jobs parallelizedConfirm it
Read the
needs:keys and anyworkflow_runtriggers across the workflow files, andgrep -rn 'max-parallel' .github/workflows/; jobs with no genuine output dependency on each other that still wait in sequence are the cause.
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.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.
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.
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.
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