When AI agents raise pull request volume, CI slows down in four places first: the machines, the toolchain, the short jobs that every other job waits for, and the setup work that each job repeats. Linear's 2026 CI rework shows that teams should fix these before they rewrite the test suite. Linear's test suites grew almost 4x, and PR wait time still fell from more than 6 minutes to just over 5. Runner time per test fell by about half (Linear).

The key numbers

All numbers in this table come from Linear's engineering post (Mufeez Amjad, September 21, 2026), except the agent share, which comes from GitHub.

Change Before After Result
Move to third-party runners GitHub-hosted Faster CPU, storage, cache Jobs 34% faster on average; tsc 52% faster
Switch tsc to tsgo tsc tsgo Weekly median of tsc check down 73%
Rewrite lint rules to use syntax only Type-aware ESLint ESLint without TypeScript API lint down 68%; full-repository lint down 55%
Shorten change detection 26 s median, 31 s p90 8 s median, 12 s p90 About 3x faster at the median
Shorten the slowest gate 94 s 20 s 74 s off the critical path
Batch short checks into two jobs Many jobs 2 jobs About 87,000 runner-minutes saved per month (11.8% of CI)
Move a write off the merge path On merge path Off merge path 42 s saved per API PR and merge-queue entry
Vitest isolate: false for opted-in files 32.8 min API-shard runner time per run 22 min About 33% less runner time per run
Agent share of PRs on GitHub Fewer than 1 in 10 (a year earlier) 1 in 3 Source: GitHub

The 33% figure is our calculation: (32.8 - 22) / 32.8 = 0.33. Linear reports the same change as "roughly 17% in monthly savings", which uses a different base.

Linear's CI time reductions by changetsc check with tsgo (weekly median)tsc check with tsgo (weekly median): 73%73%API lint (syntax-only rules)API lint (syntax-only rules): 68%68%Full-repository lintFull-repository lint: 55%55%tsc on new runnerstsc on new runners: 52%52%Per-shard setupPer-shard setup: 44%44%Average job on new runnersAverage job on new runners: 34%34%Each value has its own baseline. Do not add them. Per-shardsetup is Linear's 'roughly 44%' figure.
CHARTLinear's CI time reductions by change. Source: Linear, AI coding has made CI a bottleneck (September 2026).

Why agent volume turns CI into a bottleneck

GitHub reports that one in three pull requests on its platform now involves an AI agent. A year earlier, the number was fewer than one in ten (GitHub). Each of those PRs runs the full pipeline. Agents also write tests. Linear says agents now write the majority of its tests, and the team adds about 2,000 tests a week.

The PR wait gain looks small: from more than 6 minutes to just over 5. Is that number real? The better comparison is what would have happened with no work. Linear estimates that today's test suite would take about 11 minutes on the old pipeline. That is close to double the current wait. The rework did not only make CI faster. It kept CI flat while the work it does grew almost 4x.

For the cost side of agents in CI (token budgets, flaky test budgets), see Coding Agents in Continuous Integration. For how to measure whether more agent PRs mean more useful output, see Measuring AI Coding Assistant Productivity. This post covers only CI time.

The critical path: why small gate jobs cost more than their size

The critical path is the longest chain of jobs that must run one after another. The PR wait time can never be shorter than this chain. A job that is not on the critical path can get slower and the developer does not wait longer. A job on the critical path adds every second to the wait.

A gate job is a short job that other jobs wait for. Examples are change detection ("did the API code change?") and cache checks. Linear gates its test jobs at the job level. Skipped work then never reserves a runner. The cost is that the gates are now directly on the critical path. In Linear's words, "None of the eight API test shards can start until they finish."

Linear's critical path for an API pull request looks like this:

Order Stage Runs in parallel? What a 1-second delay costs
1 Gate jobs (path checks and cache checks) No, all shards wait 1 s on every API PR
2 Shard setup (checkout, install, database) Yes, 8 copies 1 s of wait, 8 s of runner time
3 Test run per shard Yes, 8 copies Only the slowest shard counts for wait
4 Merge-path steps No 1 s on every PR and merge-queue entry

Worked example: a change-detection gate

Linear's post says the path and cache checks block the eight shards. It then describes the change-detection job, but it does not say directly that this job is the one in front of all eight shards. The example below shows what the change-detection numbers would mean for a gate job that sits in front of every shard.

  1. The median change-detection run fell from 26 to 8 seconds. That is 18 seconds saved.
  2. The job runs once per PR, so the runner time saved is only 18 seconds per run.
  3. If no shard can start before this job ends, all eight shards start 18 seconds earlier.
  4. The developer then waits 18 seconds less. That is about 6% of a 5-minute wait (18 / 300).

A gate's runner cost is small. Its wait cost is the full delay, on every PR. The rule is the same as for setup: a second saved in a serial step is worth more than a second saved in one parallel shard.

The slowest gate fell from 94 to 20 seconds. The p90 of change detection fell from 31 to 12 seconds, and the slowest run from 138 to 37 seconds.

Gate and checkout jobs before and after (seconds)BeforeAfter0 s50 s100 s150 sSlowest gate jobSlowest gate job - Before: 94 sSlowest gate job - After: 20 s94 s20 sChange detection (slowest run)Change detection (slowest run) - Before: 138 sChange detection (slowest run) - After: 37 s138 s37 sChange detection (p90)Change detection (p90) - Before: 31 sChange detection (p90) - After: 12 s31 s12 sJobs with checkout removedJobs with checkout removed - Before: 27 sJobs with checkout removed - After: 7 s27 s7 sChange detection (median)Change detection (median) - Before: 26 sChange detection (median) - After: 8 s26 s8 s
CHARTGate and checkout jobs before and after (seconds). Source: Linear, AI coding has made CI a bottleneck (September 2026).

Fix 1: Faster runners, and the checkout cost they bring

Linear moved its jobs off GitHub-hosted runners to third-party runners with faster CPUs, faster storage, and better cache infrastructure. Jobs ran 34% faster on average. The tsc job ran 52% faster. Linear did not change one line of CI config for this gain.

Two limits apply to that 34% figure:

  • It comes from a like-for-like comparison of two days on each side of the switch. It is a short sample.
  • The new runners sit outside GitHub's network. Checkout got slower and sometimes hung.

Linear fixed the checkout problem with its own composite action. The action retries with backoff. It sets GIT_HTTP_LOW_SPEED_LIMIT and GIT_HTTP_LOW_SPEED_TIME, so a stalled connection stops after about 30 seconds. It also uses a checkout cache that keeps a persistent git mirror on a sticky disk.

If you move to third-party runners, plan the checkout fix in the same change. Measure checkout time separately from job time, or the hangs hide inside the average.

Fix 2: A faster toolchain

Linear switched its type check to tsgo, the native TypeScript compiler. The weekly median of the tsc check fell by 73%. Linear says this was "large enough to move the bottleneck off of typechecking entirely."

Lint was the next slow job. Some ESLint rules needed type information, so ESLint had to load TypeScript. Linear rewrote those rules to use static analysis of the abstract syntax tree only. The abstract syntax tree (AST) is the parsed structure of the code, with no type information. ESLint then dropped TypeScript. API lint time fell by 68%, and full-repository lint time fell by 55%. Linear later moved to Oxlint, which reduced lint runner-minutes again. Linear gives no number for that step.

tsgo and Oxlint are specific to TypeScript. The principle works in other stacks:

Linear's change Principle How to apply it in another stack
tsc to tsgo Use a faster implementation of the same checker Look for a native or compiled version of your type checker or compiler. Keep the same rules.
Lint rules on syntax only Lint must not repeat the type check Move rules that need types into the type-check job. Keep lint to rules that need only the parse tree.
ESLint to Oxlint Use a faster linter for the rules you keep Test a faster linter on the same rule set and compare findings before you switch.
Third-party runners Buy speed before you buy engineering time Benchmark one heavy job on faster hardware before you change any code.

Fix 3: Shorten gate jobs and checkout

Most of the gate-job gain came from not doing work that the gate does not need. Linear made these changes:

  1. Remove checkout from jobs that never needed it. Those jobs fell from 27 to 7 seconds.
  2. Cap the fetch depth for jobs that need only recent history.
  3. Use a sparse, blobless checkout with limited history for push and merge-queue events. This saved about 11 more seconds.
  4. Batch many short checks into two jobs. Linear estimates this saved about 87,000 runner-minutes a month, based on June usage. That is 11.8% of its total CI usage.
  5. Move a write step off the merge path. This saved 42 seconds for every API PR and merge-queue entry.

The batching number also gives a sense of scale. If 87,000 runner-minutes is 11.8% of the total, Linear's CI used about 737,000 runner-minutes a month (87,000 / 0.118). That is our calculation, not a Linear figure.

The example below shows the shape of a gate job in GitHub Actions. It is illustrative, not Linear's config. In this sketch, the needs key puts the shards behind the gate. The scripts stand in for your own checkout, install, and test commands. Check your tools' current docs for the exact flags.

jobs:
  changes:                       # gate job: every API shard waits for it
    runs-on: ubuntu-latest
    outputs:
      api: ${{ steps.detect.outputs.api }}
    steps:
      # Capped history, or a sparse, blobless checkout.
      # Skip checkout entirely if the gate does not read files.
      - run: ./scripts/checkout-minimal.sh
      - id: detect
        run: ./scripts/detect-changes.sh >> "$GITHUB_OUTPUT"

  api-tests:
    needs: changes
    if: needs.changes.outputs.api == 'true'
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4, 5, 6, 7, 8]
    steps:
      - run: ./scripts/checkout-minimal.sh
      - run: ./scripts/install-api-deps.sh                      # install only the API package
      - run: ./scripts/run-api-shard.sh ${{ matrix.shard }} 8   # shard N of 8

Fix 4: Cut setup cost per shard

Each shard repeats the same setup. Linear states the rule directly: "doubling the shard count also doubles the workflow time spent on setup." So Linear cut setup before it added shards.

Setup step Before After
pnpm install (restricted to the API package) 44-73 s 16-18 s
Database setup per container About 12 s 1-2 s
Postgres client install per shard 7-8 s 0 s (moved into the base image)
Total setup per shard 110-140 s 67-73 s

One result is not obvious. A cached node_modules took about 28 seconds to restore. A filtered install took about 7.5 seconds. The cache was slower than the work it saved. Time each cache restore against the step it replaces.

Linear's post gives a second, lower setup figure ("around 40 seconds") in a later section and does not explain the gap. This post uses the 67-73 second range from the setup section.

Worked example: why setup decides the shard count

  1. Before the work, Linear ran 4 shards and spent 8.3 minutes on setup in total.
  2. That is about 2.1 minutes of setup per shard (8.3 / 4).
  3. If Linear had doubled to 8 shards with no setup work, setup would total about 16.6 minutes (8 x 2.1).
  4. After the work, Linear ran 8 shards with 7.5 minutes of setup in total.
  5. So Linear doubled the shards and spent less total setup time than before.

Steps 2 and 3 are our calculations from Linear's two totals.

Fix 5: The test suite, after the setup work

With cheap setup, Linear moved from 4 to 8 shards. The first benchmark was about 19% faster and 19% cheaper. The slowest shard fell from 5.25 to 4.33 minutes.

Then Linear changed how Vitest isolates tests. In Linear's setup, test files ran isolated. Linear created an opt-in Vitest project with isolate: false. A file joins the project only if it has an opt-in comment. Files that use fake timers or shared state stayed isolated.

Linear calls this its "largest single performance improvement." The slowest shard fell from about 300-379 seconds to about 195 seconds. Total API-shard runner time fell from about 32.8 to 22 minutes per run.

Two details matter for teams with high agent volume:

  • Vitest splits work by file, not by test duration. One slow file can make one shard the slowest. That shard sets the wait time.
  • Agents write most of Linear's new tests. Linear updated its agent skills so that new test files follow the opt-in rule. If agents write your tests, put your CI rules in their instructions too. See Agent Skills Over Prompts.

So was the test suite the biggest win? For one change, yes. But it came last, and it depended on the earlier work. Linear says, "Some of our earliest gains required almost no optimization of CI itself," and "The setup optimizations we referred to earlier are what made eight shards practical." The correct order is cheap fixes first, then gate and setup work, then the test suite.

How to find the gate jobs on your own critical path

Do this before you change any code. It takes an afternoon.

  1. Export the job graph from your workflow files. Record each job's needs list.
  2. Collect durations for the last 2-4 weeks of runs from your CI provider's job timing data.
  3. Use the median duration per job. For a matrix job, use the slowest shard of each run.
  4. Compute the longest path through the graph. That path is your critical path.
  5. For each job on the path, count how many jobs wait for it.
  6. Mark each job that is short but has many waiting jobs. These are your gate jobs.
  7. For each gate job, list every step and ask if the gate needs it. Start with checkout and install.

The script below does steps 4 to 6. It reads a GitHub Actions workflow file and a JSON file of median durations in seconds, keyed by job id.

import json
import sys
from functools import lru_cache

import yaml

workflow = yaml.safe_load(open(sys.argv[1]))
durations = json.load(open(sys.argv[2]))  # {"changes": 26, "api-tests": 300, ...}
jobs = workflow["jobs"]


def needs(job):
    value = jobs[job].get("needs", [])
    return [value] if isinstance(value, str) else value


@lru_cache(maxsize=None)
def finish(job):
    # Earliest finish = latest finish of the jobs it needs + its own duration.
    start = max((finish(dep) for dep in needs(job)), default=0)
    return start + durations.get(job, 0)


last = max(jobs, key=finish)
path = [last]
while needs(path[-1]):
    path.append(max(needs(path[-1]), key=finish))
path.reverse()

waiting = {job: [other for other in jobs if job in needs(other)] for job in jobs}

print(f"Critical path: {finish(last):.0f} s")
for job in path:
    print(f"  {job:30} {durations.get(job, 0):6.0f} s   jobs waiting: {len(waiting[job])}")

Run it with python critical_path.py .github/workflows/ci.yml durations.json. This script counts only direct dependents. A matrix job counts as one job, so read "jobs waiting: 1" on a gate as "every shard of that job waits".

The order to apply the fixes

This list goes from least work to most. Each step uses a number from Linear as the benchmark.

  1. Benchmark your heaviest job on faster runners. If the job is 30% faster or more, move. Fix checkout in the same change.
  2. Switch to a faster type checker or compiler if your stack has one. Linear's benchmark: 73% off the tsc check.
  3. Remove type information from lint rules. Linear's benchmark: 68% off API lint.
  4. Find the gate jobs with the script above. Remove checkout and install from gates that do not need them.
  5. Batch short checks that each pay for a runner start. Linear's benchmark: 11.8% of total CI usage.
  6. Move writes and uploads off the merge path. Linear's benchmark: 42 seconds per PR.
  7. Time each setup step per shard. Delete caches that are slower than the step they replace.
  8. If setup per shard is now low, add shards. If it is not, go back to step 7.
  9. Change test isolation last, with an opt-in rule per file. Tell your agents about the rule.

This order is the wrong choice in some cases:

  • If one test file takes most of a shard's time, fix that file first. No runner or gate fix will help.
  • If your PR volume is low and CI already finishes in 2-3 minutes, the engineering time may cost more than the wait.
  • If your security policy does not allow runners outside your CI provider, skip step 1 and start at step 2.
  • If tests share state, do not turn off isolation. Fix the shared state first, or leave those files isolated as Linear did.

For the review side of a high-volume merge path, see Reviewing AI Generated Code at Scale.

Keep CI ahead of your coding agents

If your agents write more code each month, your CI decides how much of that code ships each day. OpenNash puts forward-deployed engineers on your team, and we work with Fortune 2000 companies. A typical engagement for this problem follows these steps:

  1. Measure the current pipeline: map the critical path, the gate jobs, and setup cost per shard.
  2. Apply the cheap fixes first: runners, toolchain, and gate jobs, with before-and-after numbers for each.
  3. Put CI rules into your agents' instructions and skills, so new tests follow them.
  4. Add cost and wait-time dashboards with alerts when a job moves onto the critical path.
  5. Train your platform team to run the same analysis each quarter.

At the end, you own the workflow changes, the analysis scripts, the dashboards, and the agent instructions.

Book a call with an OpenNash forward-deployed engineer or email us at hello@opennash.com. In the first session, we review your pipeline data and show which fixes give the most developer time back for the least engineering work.