Scaling CI Across Many Repositories Without Burning the Budget In short: Every gate in this chapter is worth having and none of them are free. Run only what a change can affect, cache aggressively, and split fast checks from slow ones — otherwise the pipeline gets slow, people route around it, and the gates stop mattering. Rigorous CI has a failure mode that is not technical. When the pipeline takes forty minutes, developers stop opening small pull requests, start batching changes, and eventually someone gets admin rights to merge past a red build "just this once". A gate that is too slow to respect is not a gate. So cost and speed are correctness concerns, not budget concerns. The naive pipeline A README typo triggers: install dependencies, build every library and application, run every unit test, build Storybook, launch Chromium, capture several hundred screenshots across three themes and three widths, run on all of them, and snapshot the API. Twenty-five minutes, for a typo. Multiply by a few hundred pull requests a week. Run only what is affected The single biggest saving. Because Chapter 7 gave every project explicit tags and enforced boundaries, the dependency graph is accurate — which is what makes this safe: A change in cannot affect the dispatch dashboard. The boundary rules make that a fact rather than a hope, so skipping it is not a gamble. For the expensive gates, compute the target list first: Two things happen here. Unaffected projects are skipped entirely, and the affected ones run in parallel across matrix jobs rather than in sequence. Wall-clock time drops even when total compute does not. Fail fast, in stages Order the gates by cost. There is no point paying for Chromium if lint is going to fail. The developer who wrote finds out in ninety seconds instead of twenty-five minutes, and the expensive jobs never start. Cache everything cacheable Task-level caching is the highest-value item after affected execution. If a project's inputs have not changed, its build and test results are replayed from cache rather than recomputed — including across branches, since the cache key is content-based. Use and, for the containerised jobs, a pinned image that already contains browsers so you are not downloading Chromium on every run. Split by schedule Not everything belongs on every pull request: Gate When --- --- Lint, typecheck, unit tests Every PR, affected only API diff Every PR touching a library Visual — default theme, two widths Every PR, affected only Visual — all themes, all widths Merge to , and nightly Accessibility — component stories Every PR, affected only Accessibility — full page suite Merge to End-to-end journeys Merge to , and nightly , licence check Nightly, plus release The principle: a pull request runs what could plausibly be wrong; runs everything. A regression caught two hours after merge is a fix; a regression caught after release is an incident. Neither is as bad as a pipeline nobody waits for. Self-hosted runners, honestly For a large workspace, self-hosted runners are usually cheaper per minute and can keep a warm cache and pre-pulled images. They also come with real costs: someone has to patch them, secure them, and be on call when the queue backs up. Do the arithmetic before assuming. If your hosted bill is a few hundred a month, a self-hosted fleet is likely to cost more once you include the engineer maintaining it. If it is thousands, it becomes worth modelling. The reliable savings, in order: 1. Affected-graph execution — usually the largest, by a wide margin. 2. Task caching — large and nearly free to enable. 3. Staged pipelines — saves compute on failing PRs, which are a big share of runs. 4. Scheduling the heavy suites — moves cost off the critical path. 5. Runner choice — last, and only after the above. Most teams reach for step 5 first because it is a purchasing decision rather than an engineering one, and get the smallest saving. Measure the pipeline You cannot manage what you do not look at. Track four numbers: Median PR pipeline time. Above ten minutes and behaviour starts changing. Cache hit rate. Below 60% means your keys are wrong. Flaky test rate. Anything above about 1% erodes trust in every gate. Compute minutes per merged PR. The cost metric that actually matters. Quarantine flaky tests rather than tolerating them. One test that fails randomly teaches everyone that red builds are sometimes meaningless, which is exactly the belief that lets a real failure through. "But I thought thorough CI just meant running everything" This sounds like the safe, conservative position — run more, catch more, and the only cost is money. The cost is not money. It is the pipeline's authority . Trace what actually happens as duration grows: At five minutes, developers wait for the build. They open small pull requests, because feedback is cheap. At twenty-five minutes, they context-switch while waiting, and start batching changes to amortise the wait — so pull requests get large,…