2026-10-11 18:02 UTC

Linear claims its CI redesign roughly halved runner time per test and reduced PR waits from over six minutes to just over five despite an almost fourfold increase in test suites, demonstrating a practical response to agent-driven validation load.

state: resolvedheat: lowuncertainty: lowconvergesscott: mediumagentic-coding ci-infrastructure ai-infrastructureLinearMufeez Amjad

What is this?

Linear is a software-development coordination platform led by cofounder and CEO Karri Saarinen; the supplied snippets describe its move toward workflows where agents handle coding and coordination alongside humans. The case attributes a CI redesign to Linear, claiming roughly half the runner time per test and PR waits falling from over six minutes to just over five despite nearly quadrupled test suites. However, the retrieved snippets do not contain that engineering report, substantiate those measurements, or establish Mufeez Amjad’s role; they support only the broader agent-adoption context, not the specific redesign or its results.

Why it matters to Scott

Linear’s attributed CI redesign converges with Scott’s argument in Waterfall Per Increment that cheaper agent-generated code shifts the binding constraints toward verification, offering a potential publishing comparison about the infrastructure needed to keep verification loops fast. The supplied grounding does not substantiate the redesign or its metrics, so this remains a lead rather than a validated receipt; the radar’s Anthropic CI test-selection redesign page tracks a related development, not this Linear report.
ip:source.waterfall-per-increment-how-agentic-coding-changes-everything-ebookip:concept.test-first-agent-workflowradar:anthropic-ci-test-selection-redesign
queries asked of Scott's wikis
  • coding agent throughput verification bottlenecks
  • agent harness test feedback loop latency
  • CI runner costs parallelization critical path
  • pre-PR validation versus CI quality gates
  • agent-generated code test suite growth

Measured heat

no measured readings yet — the hourly heat pass fills this in

How the heat travelled

09-20 14:00⭐ origin echo-reconstructedLinear reports nearly quadrupled test suites, roughly halved runner time per test, and lower PR wait times after runner, tooling, critical-p
Mufeez Amjad, Linear on blog (echo) · attributed from hn.story.49792067
—
09-21 19:23first on hacker news · published · +29.4hAI coding has made CI a bottleneck, so we reworked ours to keep up
julian_digital
—
09-21 19:23amplified on hacker news 👑hn.story.49792067
julian_digital
peak 295 · 362 comments · 100% of case engagement
09-21 20:20our radar first saw it · +30.4hdiscovery anchor: hn.story.49792067—

Evidence (2) — ⭐ canonical anchor

sourceobjectauthorscorecomments
🟧 hnAI coding has made CI a bottleneck, so we reworked ours to keep up
Retrieved article excerpt

Open article · Retrieved 2026-09-21T20:23:35.408207+00:00

1. [Now](https://linear.app/now)
2. [From the team](https://linear.app/now/team)

# AI coding has made CI a bottleneck, so we reworked ours to keep up

Animated vertical lines accelerating and converging from left to right on a dark background

Animated vertical lines accelerating and converging from left to right on a dark background

[Mufeez Amjad](https://linear.app/now/author/mufeez-amjad)·September 21, 2026

Earlier this year, I opened Linear to find that Tuomas, our CTO, had assigned an issue to me, titled “CI costs are high.” While I was at it, he also wanted me to make CI faster.

Agents have made it exponentially faster to ship code, but validating those changes hasn’t quite kept up at the same rate. Every PR still has to pass through CI, so as development accelerates, CI becomes a bottleneck, driving up infrastructure costs and leaving developers and agents waiting longer for feedback.

In our pursuit to make CI more performant at Linear, we optimized for how long a PR waits on CI and how much runner time it consumes. Despite our test suites almost *quadrupling* since the start of the year, we brought pull request wait time down from more than 6 minutes to just over 5, while cutting runner time per test roughly in half.

Performance metrics chart showing test coverage increase (blue line) and machine time reduction (white line) from January through September.

Performance metrics chart showing test coverage increase (blue line) and machine time reduction (white line) from January through September.

This is test suite performance indexed to the first week of January. The white line, tracking machine time per test, spikes when we added test shards, which shorten the wait and costs more machine time, and again during checkout stalling issues

Broadly, we improved CI in four ways:

- Upgraded infrastructure and tooling
- Optimized the jobs that gate other work
- Reduced repeated setup
- Made test execution more efficient

Linear’s codebase is primarily TypeScript, but many of these optimizations apply across languages and toolchains.

### Upgraded infrastructure and tooling[⁠](https://linear.app/now/ci-bottleneck-reworked#upgraded-infrastructure-and-tooling)

Some of our earliest gains required almost no optimization of CI itself. Moving our workloads off GitHub Actions to third-party runners with faster CPUs, higher-performance storage, and better cache infrastructure gave us faster machines to run the same pipeline on. In a like-for-like comparison of the two days either side of the switch, jobs ran 34% faster on average, with some workloads like `tsc` dropping 52%.

Separately, modernizing our toolchain also paid off. Switching to `tsgo`, the native TypeScript compiler, cut the weekly median of the `tsc` check by 73%, large enough to move the bottleneck off of typechecking entirely.

#### Lint without the type checker[⁠](https://linear.app/now/ci-bottleneck-reworked#lint-without-the-type-checker)

Linting was another early target. A handful of our custom lint rules depended on TypeScript type information, either to enforce a restriction or apply an autofix. That meant every lint run had to build the full type graph before evaluating those rules, making linting one of our most memory-intensive CI jobs.

We rewrote the rules to use static analysis over the abstract syntax tree, identifying function-like constructs and guard patterns without type information. That let ESLint drop TypeScript entirely, reducing API lint time by 68%, and full-repository lint time by 55%. Memory usage dropped substantially as well.

Removing the dependency on type information also made our later move to [Oxlint](https://oxc.rs/docs/guide/usage/linter) much easier because rules that operate purely on syntax are straightforward to port. Oxlint itself reduced the CI runner-minutes spent on linting.

### Optimize the jobs that gate other work[⁠](https://linear.app/now/ci-bottleneck-reworked#optimize-the-jobs-that-gate-other-work)

With the underlying infrastructure and individual checks running faster, we zoomed out to look at CI as a system. That drew our attention to the small jobs that sat in front of everything else. Every run starts by checking which paths a PR touched and whether these tests have already passed for the same inputs. We gate on those checks at the job level so skipped work never reserves a runner, but that also puts them directly on the critical path. None of the eight API test shards can start until they finish, making even small delays disproportionately important.

#### Fetch only what each job needs[⁠](https://linear.app/now/ci-bottleneck-reworked#fetch-only-what-each-job-needs)

Several of our workflows start with a change-detection job that decides what runs next; for instance, it checks whether a diff contains a database migration and outputs a signal used to schedule the relevant database CI checks. These jobs were checking out the full working tree even though they needed only a small subset of it. We capped the fetch depth, which took the slowest of these gates from 94 seconds to 20, and removed checkout entirely from the jobs that never needed a working tree, reducing time spent on those from 27 seconds to 7. For commit push and merge-queue events, where we do have to diff paths, we found that a sparse, blobless checkout with limited history was enough, saving another 11 odd seconds.

Performance distribution histogram: blue bars (After 8s median) and gray bars (Before 26s median), showing reduced load times after optimization.

Performance distribution histogram: blue bars (After 8s median) and gray bars (Before 26s median), showing reduced load times after optimization.

The median duration of the change-detection job fell from 26 to 8 seconds, p90 from 31 to 12 seconds, and the slowest run from 138 to 37 seconds.

#### Make checkout more resilient[⁠](https://linear.app/now/ci-bottleneck-reworked#make-checkout-more-resilient)

After we swapped the underlying runner infrastructure, we noticed that our checkout times (with `actions/checkout`) in our jobs had gotten longer and would sometimes hang. Because the third-party runners sit outside GitHub’s network, they rely on a direct IP link to reach GitHub. The provider traced the hangs to intermittent degradation on that link. Several of our workflows begin with a checkout, so a stalled fetch could delay the entire CI run.

To be resilient to the network instability, we replaced `actions/checkout` with a composite action of our own that retried with backoff, and sets `GIT_HTTP_LOW_SPEED_LIMIT` and `GIT_HTTP_LOW_SPEED_TIME` so a stalled connection aborts after about 30 seconds instead of hanging and also uses the checkout cache, which keeps a persistent git mirror on a sticky disk. The result was far fewer runs where a critical-path job sat idle waiting for checkout to finish.

#### Minimize what’s on the critical path[⁠](https://linear.app/now/ci-bottleneck-reworked#minimize-what's-on-the-critical-path)

Not every job on the critical path needed to be there. We were writing cache markers as part of the final check before merging, which meant a pull request could sit in the merge queue even after its tests had passed. We moved that write into a job that runs once the test shards finish but gates nothing, shaving 42 seconds from the merge path for every API pull request and merge-queue entry.

Together, these changes took roughly a minute off the required check for API pull requests on cache misses, while also reducing runner starts.

### Reduce repeated setup[⁠](https://linear.app/now/ci-bottleneck-reworked#reduce-repeated-setup)

From there, we turned to the setup cost repeated across every job, like booting a runner, installing packages, and provisioning build dependencies. That overhead means a job that does only seconds of useful work can end up consuming whole minutes of infrastructure time. Here are a few steps we took to work around that issue:

#### Preinstall shared dependencies in the CI image[⁠](https://linear.app/now/ci-bottleneck-reworked#preinstall-shared-dependencies-in-the-ci-image)

Our API test shards each spent 7 to 8 seconds installing the same Postgres client with apt on every run. We moved it into a small CI base image containing Node and the client, so each shard could start from an environment that was ready to run. We later added the required native build headers to the image after discovering that downloading them during setup could occasionally hang, shortening the tail.

#### Install only the dependencies each job needs[⁠](https://linear.app/now/ci-bottleneck-reworked#install-only-the-dependencies-each-job-needs)

Linear’s codebase is a monorepo managed as a pnpm workspace. Our API test workflow was installing the entire workspace even though it only needed the API package and its dependencies. Restricting the install to our API package cut pnpm install from 44-73 seconds to 16-18 seconds. We applied the same pattern to API-adjacent jobs, which were each installing the full repository and uploading a dependency cache that later runs almost never hit.

#### Don’t cache when it’s faster to rebuild[⁠](https://linear.app/now/ci-bottleneck-reworked#don't-cache-when-it's-faster-to-rebuild)

We also tested caching `node_modules` and found it was faster to rebuild. The cache key depended on a frequently changing lockfile, and even a cache hit took about 28 seconds to restore, compared with roughly 7.5 seconds for a filtered install. The cache was adding save time and variability without giving us any discernible advantage.

Together, these three changes reduced per-shard setup time by roughly 44%, from 110-140 seconds to 67-73 seconds.

GitHub Actions workflow comparison showing test-api runs with step-by-step timing breakdown; left run (4m 57s) versus right run (3m 14s), demonstrating performance improvements across pipeline stages

GitHub Actions workflow comparison showing test-api runs with step-by-step timing breakdown; left run (4m 57s) versus right run (3m 14s), demonstrating performance improvements across pipeline stages

p95 durations of a test shard

Beyond this, there were other forms of repeated setup we could avoid altogether.

#### Avoid replaying unchanged setup[⁠](https://linear.app/now/ci-bottleneck-reworked#avoid-replaying-unchanged-setup)

Some setup work only needs to be repeated when its inputs change. Our API containers, for example, were replaying the full database migration history on every run, even when a PR hadn’t changed the schema. For those cases, we switched to loading a generated schema snapshot and bootstrap file instead, cutting database setup from roughly 12 seconds to 1-2 seconds per container.

#### Batch short checks into fewer jobs[⁠](https://linear.app/now/ci-bottleneck-reworked#batch-short-checks-into-fewer-jobs)

Seven independent checks were each starting a runner, checking out the repository, and installing dependencies before doing only seconds of useful work. We consolidated them into two jobs, and then ran the seven tasks concurrently inside them. That reduced the number of times we paid the same setup overhead from seven to two. Based on June usage, the change saved roughly 87,000 runner-minutes per month, equivalent to 11.8% of our total CI usage.

Before and after comparison of CI/CD pipeline batching optimization, showing test workflow steps with execution times and dependencies, with all tasks marked as successfully completed

Before and after comparison of CI/CD pipeline batching optimization, showing test workflow steps with execution times and dependencies, with all tasks marked as successfully completed

### Make test execution more efficient[⁠](https://linear.app/now/ci-bottleneck-reworked#make-test-execution-more-efficient)

With the fixed cost of each test shard down, we could afford to parallelize the API suite more aggressively. It was the largest and one of the most frequently executed parts of our workflow, so improvements the
julian_digital313407
🟧 echo.blog ⭐Linear reports nearly quadrupled test suites, roughly halved runner time per test, and lower PR wait times after runner, tooling, critical-pMufeez Amjad, Linear——

Interpretation history

Decision trace