Agent-Written Code Made CI the Bottleneck, and Per-Job Setup Is Where the Money Goes
Linear's test suites nearly quadrupled this year while runner time per test fell by half. The changes that did the work had almost nothing to do with the tests.
Linear is adding roughly 2,000 tests a week. Not because someone launched a testing initiative, but because agents write the majority of their tests now, and agents do not get tired of writing tests. The company published the numbers yesterday, and the interesting part is not that CI got slower. Of course it got slower. The interesting part is where the time actually went once they measured it, because it was not in the tests.
At 110 to 140 seconds of setup per test shard, running eight shards would have burned 15 to 19 minutes of runner time before a single assertion executed. More than the tests themselves cost. That one sentence reorganizes how you should think about CI in a codebase where agents are producing the work.
Here is the claim I want to defend: when agents write the code, the dominant cost in your pipeline stops being how long the tests take and becomes how much it costs to stand up the environment they run in. Fixed setup cost is the thing that decides how far you are allowed to parallelize, and parallelization is the only lever that scales with an agent's output. Optimize the tests and you get a linear win. Optimize setup and you get a multiplier.
Why the shape of the problem changed
Pre-agent, a test suite grew at roughly the speed a team could write tests, which is slow and roughly constant. You added tests, the suite got slower, somebody eventually sharded it, and that held for another year.
Agents broke the constant. Linear's suites almost quadrupled since January. The growth rate of validation work is now tied to the throughput of a machine rather than the headcount of a team, and there is no reason to expect it to level off.
That changes which optimizations matter. If your suite doubles every few months, a 20% improvement in test execution buys you a quarter. Sharding buys a different class of relief, because you can keep adding shards. Except you can't, and the reason is the setup tax.
Every shard you add pays the full fixed cost again: boot a runner, check out the repo, install dependencies, provision the database. Double the shards, double the money you spend on setup. So the ceiling on parallelism is set entirely by how cheap it is to start one shard. Linear's numbers make this concrete. They had been running four shards at 110 to 140 seconds of setup each, about 8.3 minutes of setup total. After the setup work, eight shards cost 7.5 minutes of setup, less than four shards used to cost, while running the tests twice as wide.
They did not make sharding faster. They made sharding affordable, and then sharded.
The four moves, ranked by what they actually bought
Linear grouped its work four ways: upgraded infrastructure, optimized gating jobs, reduced repeated setup, and more efficient test execution. Read as a shopping list, the ordering by payoff is worth stealing.
Toolchain swaps paid first and cost the least thought. Moving off GitHub Actions to third-party runners with faster CPUs and better caching made jobs 34% faster on average, with tsc alone dropping 52%. Switching to tsgo, the native TypeScript compiler, cut the weekly median typecheck by another 73%, enough that typechecking stopped being the bottleneck at all. Neither required understanding the pipeline. They are a purchase order and a dependency bump.
Decoupling lint from type information was the sleeper. A handful of custom lint rules needed TypeScript type data, so every lint run built the full type graph first. Rewriting those rules to work on the abstract syntax tree instead cut API lint time by 68% and full-repository lint by 55%, with memory dropping substantially. It also made a later port to Oxlint straightforward, because rules that only touch syntax are portable and rules that need the type checker are not.
The gating jobs cost more than anyone budgeted for. Small change-detection jobs sat in front of eight API test shards, so every second they took multiplied across everything downstream, and they were checking out the entire working tree to look at a diff. Capping fetch depth took the slowest gate from 94 seconds to 20. Dropping checkout from jobs that never needed a working tree took those from 27 seconds to 7.
Batching tiny jobs was the biggest pure-cost win. Seven independent checks were each booting a runner, checking out the repo, and installing dependencies to do a few seconds of real work. Consolidating them into two jobs running the seven tasks concurrently saved about 87,000 runner-minutes per month, 11.8% of total CI usage. Nothing got faster. They stopped paying the same entry fee seven times.
The single largest performance gain came from somewhere else, and it comes with a warning attached.
The sharp edge: sharing module state
Vitest isolates every test file by default. Linear's isolation meant rebuilding the entity, GraphQL and decorator graph inside every shard, repeatedly, for no benefit in most files. They introduced an opt-in Vitest project with isolate: false, letting eligible files share a module registry within each worker. The slowest shard dropped from roughly 300 to 379 seconds down to about 195. Total API-shard runner time fell from about 32.8 minutes to 22. Roughly 17% of monthly cost.
Vitest's own documentation is measured about this. The isolate option defaults to true, and disabling it "might improve performance if your code doesn't rely on side effects (which is usually true for projects with node environment)." That parenthetical is doing real work. Usually true is not always true, and the failure mode when it is false is not a crash. It is a test that passes because a previous file left state behind.
Linear handled it the right way: eligibility is explicit, marked with an opt-in comment on every single file, with teardown added for shared state. Files using fake timers or tangled shared state stayed in the isolated project. This is the correct pattern and I would not deviate from it. An allowlist you write by hand is the only version of this that stays safe, because the moment eligibility becomes a default, someone lands a file that violates it and the suite lies to you for a month without ever going red.
The detail nobody is talking about
Buried in Linear's post is a sentence that I think matters more than any percentage in it. Because agents now write the majority of their tests, they updated their agent skills so generated tests honor the isolation opt-in by default.
Sit with that. The correctness constraint that keeps this optimization safe does not live in the linter. It does not live in a type. It lives in a markdown file that a model reads before it writes code.
That is a new category of engineering artifact and it has none of the properties we rely on. A lint rule fails the build. A type error fails the build. A skill file that drifts out of date produces plausible code violating an invariant nobody checks, and it surfaces somewhere downstream where the cause is unrecoverable. We spent thirty years moving correctness constraints from conventions into compilers, and agent skills move a category of them straight back into conventions.
I do not think this is avoidable, and I do not think Linear did anything wrong. When agents write most of the code, the cheapest place to express a constraint is the place the agent reads. But if you copy this pattern, copy it with a belt: write the skill file, then write the lint rule or the test that catches the skill file being ignored. Treat the skill as documentation of an invariant, never as the enforcement of one.
Put this into practice
You do not need Linear's scale for most of this. Here is the order I would actually work in, cheapest first.
Measure setup separately from execution. Before optimizing anything, get one number: what fraction of a CI run is spent before the first test executes? Most CI dashboards show total job duration and hide the split. If setup is over a third of the run, everything below is worth doing and you should skip the test-level tuning entirely for now.
Audit what your gating jobs check out. Any job that only reads a diff, a path list, or a cache key almost certainly does not need a full working tree. Cap the fetch depth or drop checkout entirely. This is an afternoon and the payoff multiplies across every job waiting behind it.
Count how many times you pay the same setup. List every job that installs dependencies. If several of them do seconds of real work, consolidate them and run the tasks concurrently inside one job. This is the highest ratio of savings to risk on the list, because merging jobs cannot change a test result.
Preinstall the constant parts in a CI base image. If each shard apt-installs the same client, bake it into an image. Linear also found this shortened their tail, because a package download that occasionally hangs stops existing as a failure mode when it is not happening at runtime.
Check whether your cache is actually faster than a rebuild. Linear tested caching node_modules and found restoring a hit took about 28 seconds against roughly 7.5 seconds for a filtered install. The cache was pure overhead. This one is worth ten minutes on any repo, because cached-by-default is an assumption almost nobody re-measures.
Then, and only then, shard wider. Once setup is cheap, add shards until the marginal shard stops paying. Do the isolate: false work last, as an explicit per-file allowlist, and only if you have the discipline to keep the allowlist honest.
Honest limitations
Linear's codebase is primarily TypeScript in a pnpm monorepo, and several of these wins are shaped by that. tsgo is a TypeScript story. Filtered workspace installs are a pnpm story. The lint rewrite depends on your rules being portable to syntax analysis, and some genuinely are not: a rule that needs to know whether a value is nullable needs the type checker, full stop. If you are in Go or Rust, your fixed setup profile looks completely different and the compile cache is the thing to attack instead.
The numbers are also Linear's own, measured on their infrastructure, and two of them carry caveats the post states plainly. The chart of machine time per test spikes when shards were added and again during checkout stalling issues, so the improvement is not a clean line. And the third-party runner move introduced its own problem: because those runners sit outside GitHub's network, intermittent degradation on the direct IP link made checkouts hang, and Linear had to write a retrying composite action with GIT_HTTP_LOW_SPEED_LIMIT and GIT_HTTP_LOW_SPEED_TIME set so a stalled connection aborts after about 30 seconds. Moving off GitHub Actions is not free. It trades one set of performance characteristics for another set plus a new class of network flake you now own.
The bigger limitation is one I cannot resolve for you. Nothing in this makes CI keep up with agents indefinitely. Linear bought back roughly a minute of wait time and half the runner cost against a nearly quadrupled suite, which is an excellent trade and also a holding action. At 2,000 tests a week, the setup optimizations that made eight shards affordable will need to make sixteen affordable, and there is a floor under how cheap booting a machine can get. Somebody is eventually going to have to answer the harder question, which is whether every test an agent writes deserves to run on every pull request. Nobody has published a good answer to that yet, and the people claiming test selection solves it are mostly selling test selection.
What to do with this
The reframe is the takeaway, and it costs you nothing to adopt. Stop thinking of CI as a thing that runs your tests and start thinking of it as a thing that pays an entry fee, many times, on your behalf. Then go find out what your entry fee is.
If you only do one thing this week, get the setup-versus-execution split for your slowest required check. That single number will tell you whether you have a test problem or an infrastructure problem, and in a codebase where agents are writing the tests, I would bet on the second one.
Sources: Linear's engineering post by Mufeez Amjad, September 21, 2026; Vitest isolate configuration reference; Oxlint documentation. All performance figures are Linear's own measurements on their own infrastructure.
Medium metadata
- Title: Agent-Written Code Made CI the Bottleneck, and Per-Job Setup Is Where the Money Goes
- Subtitle: Linear's test suites nearly quadrupled this year while runner time per test fell by half. The changes that did the work had almost nothing to do with the tests.
- Tags: AI Agents, Continuous Integration, Software Engineering, Developer Tools, TypeScript
- Canonical: fervorai.dev