---
title: "Slow tests"
description: "Find where tests spend their time, from repeated setup to the longest shard. Compare changes companies made to their test workflows."
url: https://starsling.dev/ci-playbooks/categories/slow-tests
canonicalUrl: https://starsling.dev/ci-playbooks/categories/slow-tests
---

# Slow tests

Find where tests spend their time, from repeated setup to the longest shard. Compare changes companies made to their test workflows.

## Where to start

Diagnose first: is the long pole test execution, repeated setup, slow fixtures, fixed sleeps, or an imbalanced shard? Measure those phases separately. A slow test job can spend its time in fixture setup, execution, waiting, or cleanup. Measure those phases first, then choose a technique that addresses the expensive part while preserving what the suite verifies.

## Apply these techniques

````text
Evaluate this CI improvement area in my repository: Slow tests.

Find where tests spend their time, from repeated setup to the longest shard. Compare changes companies made to their test workflows.

Read the techniques and source-backed company examples: https://starsling.dev/ci-playbooks/categories/slow-tests.md

Inspect this repository’s CI workflows, recent run timings, build and test commands, and dependency graph. Identify the relevant bottleneck and rank applicable techniques using evidence from this repository.
Measure a baseline and compare the relevant techniques, prerequisites, company context, and primary sources. Rank candidate changes by fit and observable opportunity.
Recommend the smallest useful change and the verification plan. ASK ME TO APPROVE THE EXACT CHANGE BEFORE EDITING ANY FILES.
Only after approval, implement on a new feature branch and open a reviewable pull request. Never merge or push to a default or protected branch.
Preserve required checks, test coverage, release and deployment behavior, credential boundaries, and necessary fallback paths. Run relevant checks and compare equivalent runs for elapsed time, queue time, runner minutes, and reliability. Report any unmeasured outcomes.
````

## Techniques

17 techniques with source-backed company examples.

### [Split long test suites across runners](https://starsling.dev/ci-playbooks/techniques/shard-test-suites)

Distribute a suite across CI jobs while keeping complete test discovery and collecting the results from every shard.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#shard-test-files)**: Use it when One test job runs a large suite on a single runner, and the suite can be split by file or run in more than one process. Check before adopting: Splitting by path balances file counts. Compare shard durations after the change, and move to duration-based splitting if one shard stays the long pole. Tests that share state need isolation before they run in parallel.
- **[DoorDash](https://starsling.dev/ci-playbooks/doordash#shard-test-phase)**: Use it when One test job runs long, or runs out of memory, after raising its worker count on one machine. Check before adopting: Every shard repeats setup, so pick the shard count by measurement, and collate coverage so the report still covers the whole suite. Adapted for GitHub Actions; compare the original system with yours.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#test-shards)**: Use it when A long test job remains the critical path after removing repeated waits and setup. Check before adopting: Each shard repeats setup and consumes runner minutes. Verify the union of tests, unique artifacts and required aggregate status.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#split-slow-job)**: Use it when One job sets the workflow's finish time and its tests fall into independent groups. Check before adopting: Each split job repeats setup and adds runner minutes.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#measure-shards)**: Use it when One test job is the longest in the workflow after its tests are isolated and its worker count is tuned. Check before adopting: Each shard repeats checkout, install and authentication, so total runner minutes can rise while elapsed time falls.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#shard-e2e)**: Use it when A test suite runs serially because its tests share a fixed resource on one machine, and it takes most of the pull request run. Check before adopting: Each shard repeats checkout and setup, and the slowest test file sets the floor for every shard.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#shard-with-complete-discovery)**: Use it when One long test job remains the bottleneck after setup and worker tuning. Check before adopting: Each shard repeats setup. Verify the shards together run every original test exactly once, and keep device and diagnostic coverage.

#### Anthropic: Split test files across CI shards that stay balanced to within one file

Bun v1.3.13 added bun test --shard=M/N to split test files across CI runners, in the syntax Jest, Vitest and Playwright use. Files are sorted by path for determinism and distributed round-robin, so each shard stays balanced to within one file of the others. Inside one runner, bun test --parallel distributes files across worker processes, the CPU count by default, and idle workers steal work from the busiest remaining queue. Output stays identical to serial execution.

Evidence: Company-reported result or observation

Split test files across multiple CI runners with the new --shard flag, matching the syntax used by Jest, Vitest, and Playwright. ([Source](https://bun.sh/blog/bun-v1.3.13))

Use it when: One test job runs a large suite on a single runner, and the suite can be split by file or run in more than one process.

Before applying it: Splitting by path balances file counts. Compare shard durations after the change, and move to duration-based splitting if one shard stays the long pole. Tests that share state need isolation before they run in parallel.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#shard-test-files)

[Primary source](https://bun.sh/blog/bun-v1.3.13)

#### DoorDash: Shard the test phase across agents

**Translated to GitHub Actions**

- What DoorDash built: DoorDash added sharding to Rush: a sharded operation becomes a pre-shard node, n shard nodes and a collate node that merges coverage.
- Your GitHub Actions version: A test matrix of shards with each shard's index and its own output folder, followed by a job that merges coverage into one report.

DoorDash's tests already ran eight Jest workers per machine and had started to hit memory errors, so DoorDash added sharding to Rush upstream. A sharded operation becomes a pre-shard node that keeps the dependencies, n shard nodes, and a collate node that merges coverage into one report; each shard gets its index and its own output folder so the build cache stays valid. A follow-up clusters shards by operation name, so shards of one package can run on different agents. Four shards with seven workers each ran fastest, and every shard finished within six minutes, the same time as the build. DoorDash's Kotlin build service shards unit tests the same way.

Evidence: Company-reported result or observation

All of the test shards completed within six minutes, which tied build time and test time. ([Source](https://careersatdoordash.com/blog/how-doordash-slashed-web-developer-build-times/))

Use it when: One test job runs long, or runs out of memory, after raising its worker count on one machine.

Before applying it: Every shard repeats setup, so pick the shard count by measurement, and collate coverage so the report still covers the whole suite.

GitHub Actions adaptation: Compare the source system with your own workflow before applying this translation.

[Read more in the DoorDash playbook](https://starsling.dev/ci-playbooks/doordash#shard-test-phase)

[Primary source](https://careersatdoordash.com/blog/how-doordash-slashed-web-developer-build-times/)

#### Mastra: Split a long end-to-end suite across runners

StarSling’s agent added three Playwright shards to the kitchen-sink job, passed each shard index to the test runner and kept fail-fast false.

Evidence: Source PR/commit evidence

Mastra’s kitchen-sink E2E suite still runs sharded today, now across four shards in e2e-tests.yml. ([Source](https://github.com/mastra-ai/mastra/blob/f92e947a448a47515acf14df1eddc5fd5c36f424/.github/workflows/e2e-tests.yml))

Use it when: A long test job remains the critical path after removing repeated waits and setup.

Before applying it: Each shard repeats setup and consumes runner minutes. Verify the union of tests, unique artifacts and required aggregate status.

[Read more in the Mastra playbook](https://starsling.dev/ci-playbooks/mastra#test-shards)

[Primary source](https://github.com/mastra-ai/mastra/pull/15888)

#### OpenAI: Split the slowest job across runners

The CLI's help-compatibility workflow split its dispatch and contract tests into separate jobs on Intel macOS and Windows ARM, and moved an exhaustive flag-inventory test, which starts 482 subprocesses per platform, out of that matrix; the full Linux suite still runs it.

Evidence: Source PR/commit evidence

Workflow: 7m10s in the first successful run after merging current main, against the earlier 23-run median of 10m21s ([Source](https://github.com/openai/openai-cli/pull/511))

Use it when: One job sets the workflow's finish time and its tests fall into independent groups.

Before applying it: Each split job repeats setup and adds runner minutes.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#split-slow-job)

[Primary source](https://github.com/openai/openai-cli/pull/511)

#### Shopify: Give a long suite more than one runner

After measuring worker and shard combinations, the CLI raised Playwright's workers setting from 5 to 10 and added a two-shard matrix to its end-to-end job, giving each shard's artifacts a unique name. Each shard is a separate job that runs Playwright with the configured worker limit, so ten workers means up to ten on each machine. The default branch still runs two shards. The CLI's Windows unit tests are sharded the same way.

Evidence: Source PR/commit evidence

The PR reports ~4 min faster wall clock: 7m 44s against 11m 43s, a 34% reduction, comparing 10 workers on two shards with 5 workers on one runner. ([Source](https://github.com/Shopify/cli/pull/7350))

Use it when: One test job is the longest in the workflow after its tests are isolated and its worker count is tuned.

Before applying it: Each shard repeats checkout, install and authentication, so total runner minutes can rise while elapsed time falls.

[Read more in the Shopify playbook](https://starsling.dev/ci-playbooks/shopify#measure-shards)

[Primary source](https://github.com/Shopify/cli/pull/7350)

#### Spotify: Shard a serial end-to-end suite across runners

The confidence-cli end-to-end suite runs serially because it uses a fixed auth callback port, and it took about 4.4 minutes of a 5.5-minute pull request run. The change splits it into four matrix shards with vitest --shard, each on its own runner, and adds an aggregate E2E tests job so the check name stays the same.

Evidence: Estimate

Before the change, E2E took about 4.4 minutes of a 5.5-minute pull request run; the author estimated about 1.5 minutes after it, with the slowest test file at 74 seconds as the floor (estimate). ([Source](https://github.com/spotify/confidence-cli/pull/73))

Use it when: A test suite runs serially because its tests share a fixed resource on one machine, and it takes most of the pull request run.

Before applying it: Each shard repeats checkout and setup, and the slowest test file sets the floor for every shard.

[Read more in the Spotify playbook](https://starsling.dev/ci-playbooks/spotify#shard-e2e)

[Primary source](https://github.com/spotify/confidence-cli/pull/73)

#### Stripe: Shard a long suite across runners

Stripe Android partitions discovered PaymentSheet end-to-end test classes across five parallel workflows. Stripe React Native split its end-to-end tests into matrix shards, and Stripe iOS shards full SDK size reports into three jobs on master and releases.

Evidence: Source PR/commit evidence

Use it when: One long test job remains the bottleneck after setup and worker tuning.

Before applying it: Each shard repeats setup. Verify the shards together run every original test exactly once, and keep device and diagnostic coverage.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#shard-with-complete-discovery)

[Primary source](https://github.com/stripe/stripe-android/pull/13225)

### [Isolate state before running tests in parallel](https://starsling.dev/ci-playbooks/techniques/isolated-parallel-tests)

Give concurrent tests independent databases, services, or fixtures so they can share runner capacity without interfering with each other.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[DoorDash](https://starsling.dev/ci-playbooks/doordash#parallel-e2e)**: Use it when A browser or desktop suite runs on one worker while its tests share no files, ports or processes. Check before adopting: Parallel journeys are safe when each owns its state; a shared file, port or install step becomes a race. Measure the worker count on your own runner.
- **[Ramp](https://starsling.dev/ci-playbooks/ramp#test-databases)**: Use it when Tests run in parallel workers against one shared database or service, and adding workers leaves the job's time flat while the database is saturated. Check before adopting: Every extra database uses CPU and memory the test workers would otherwise get; Ramp saw slight regressions past its measured ratio, so keep the count that measured fastest.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#isolate-before-parallel)**: Use it when Tests run one at a time because they share ports, temporary files, accounts or database state. Check before adopting: More workers add load on shared external APIs; keep teardown observable and keep genuinely serial tests on a serial lane.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#isolate-parallel-test-state)**: Use it when A shared database, port or remote account forces the whole suite to run serially. Check before adopting: Keep intentionally shared-state tests in a serial lane, keep teardown reliable, and use isolated sandbox accounts for remote services.
- **[Uber](https://starsling.dev/ci-playbooks/uber#isolate-test-services)**: Use it when Independent integration suites collide on one port, database or container name, forcing serial execution. Check before adopting: Use collision-proof namespaces and guaranteed cleanup after every run. Separate data and image tags as well as ports, and use a protocol-level readiness probe in place of a fixed startup sleep.

#### DoorDash: Run isolated end-to-end journeys in parallel

The packaged end-to-end suite ran 66 journeys on one worker. Each journey already had its own home directory, workspace, runtime and server port, so the PR set Playwright to three workers with fullyParallel. Global setup sweeps orphaned processes once and installs the Electron runtime once, before any worker starts.

Evidence: Source PR/commit evidence

Use it when: A browser or desktop suite runs on one worker while its tests share no files, ports or processes.

Before applying it: Parallel journeys are safe when each owns its state; a shared file, port or install step becomes a race. Measure the worker count on your own runner.

[Read more in the DoorDash playbook](https://starsling.dev/ci-playbooks/doordash#parallel-e2e)

[Primary source](https://github.com/doordash-oss/agentic-orchestrator/pull/139)

#### Ramp: Give parallel test workers more databases

After the CPU change, raising the pytest-xdist worker count left the suite's time flat at first. Profiling showed the test database buckling under the load: it hit lock contention once more than 20 workers shared it. Ramp ran more test databases, measured the ratio of workers to databases, settled on about 12 workers per database, and tuned Postgres connection settings.

Evidence: Company-reported result or observation

Together with the faster CPUs, the database changes took Ramp to a ~70% speedup of the test suite two days in, a saving of about 30 minutes. ([Source](https://builders.ramp.com/post/faster-python-tests-part-two))

Use it when: Tests run in parallel workers against one shared database or service, and adding workers leaves the job's time flat while the database is saturated.

Before applying it: Every extra database uses CPU and memory the test workers would otherwise get; Ramp saw slight regressions past its measured ratio, so keep the count that measured fastest.

[Read more in the Ramp playbook](https://starsling.dev/ci-playbooks/ramp#test-databases)

[Primary source](https://builders.ramp.com/post/faster-python-tests-part-two)

#### Shopify: Isolate tests before adding workers

The CLI end-to-end suite shared one development store. Shopify/cli#7279 gave each test its own store and each worker its own ports; #7309 then turned on fullyParallel and raised Playwright workers from one to five. The default branch runs the suite fully parallel with four workers.

Evidence: Source PR/commit evidence

The PR reports about 10 minutes with 5 workers, down from about 27 minutes with 1 worker. ([Source](https://github.com/Shopify/cli/pull/7309))

Use it when: Tests run one at a time because they share ports, temporary files, accounts or database state.

Before applying it: More workers add load on shared external APIs; keep teardown observable and keep genuinely serial tests on a serial lane.

[Read more in the Shopify playbook](https://starsling.dev/ci-playbooks/shopify#isolate-before-parallel)

[Primary source](https://github.com/Shopify/cli/pull/7309)

#### Stripe: Give concurrent tests their own state

Stripe sync-engine gives each end-to-end test file its own Postgres container on a random port and enables file parallelism; webhook tests run in a separate sequential configuration because they share a remote endpoint. TaxJar, a Stripe company, made its remote transaction IDs unique per version and build so its WooCommerce 7.x and 9.x test lanes can run in parallel.

Evidence: Source PR/commit evidence

Use it when: A shared database, port or remote account forces the whole suite to run serially.

Before applying it: Keep intentionally shared-state tests in a serial lane, keep teardown reliable, and use isolated sandbox accounts for remote services.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#isolate-parallel-test-state)

[Primary source](https://github.com/stripe/sync-engine-fork/pull/124)

#### Uber: Give concurrent suites their own services

SubmitQueue replaced shared manual service setup with a Compose stack per test context. Docker allocates an ephemeral host port and the test discovers it at runtime, so the suites run as separate parallel CI jobs. OPT88's resource-isolation step is adapted; the readiness and image-tag fixes on this page complete it.

Evidence: Source PR/commit evidence

The PR creates separate CI jobs for the suites and an isolated Compose project per test context. ([Source](https://github.com/uber/submitqueue/pull/41))

Use it when: Independent integration suites collide on one port, database or container name, forcing serial execution.

Before applying it: Use collision-proof namespaces and guaranteed cleanup after every run. Separate data and image tags as well as ports, and use a protocol-level readiness probe in place of a fixed startup sleep.

[Read more in the Uber playbook](https://starsling.dev/ci-playbooks/uber#isolate-test-services)

[Primary source](https://github.com/uber/submitqueue/pull/41)

### [Balance test work across shards](https://starsling.dev/ci-playbooks/techniques/balance-test-shards)

Use measured runtimes or smaller test files to reduce the slowest shard, which determines when the suite finishes.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Linear](https://starsling.dev/ci-playbooks/linear#balance-the-shards)**: Use it when A few files dominate a file-based shard assignment. Check before adopting: Preserve test discovery, assertions and artifacts. Vitest assigns work by file, so one large file still sets its shard’s time.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#balanced-shards)**: Use it when A test job dominates the critical path, or existing shards finish at very different times. Check before adopting: Balance shard duration and total runner minutes together. A checked-in timing table goes stale as new tests arrive, so refresh it or use the runner's own timing data.
- **[Ramp](https://starsling.dev/ci-playbooks/ramp#pack-tests-by-duration)**: Use it when A test job runs in parallel workers or matrix shards, and the last one regularly finishes well after the first. Check before adopting: A packing algorithm that uses test timings needs them recorded; refresh them as tests are added so new slow tests spread across the workers.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#rebalance-and-cover-shards)**: Use it when One shard sets the finish time while others wait idle. Check before adopting: Keep the union of test IDs identical and track new, renamed and deleted tests.

#### Linear: Balance test files before buying more shards

Linear split oversized test files into smaller files, then benchmarked shard and runner configurations. Moving from four to eight shards made the critical job roughly 19% faster and 19% cheaper in the initial benchmark.

Evidence: Documented technique

Use it when: A few files dominate a file-based shard assignment.

Before applying it: Preserve test discovery, assertions and artifacts. Vitest assigns work by file, so one large file still sets its shard’s time.

[Read more in the Linear playbook](https://starsling.dev/ci-playbooks/linear#balance-the-shards)

[Primary source](https://linear.app/now/ci-bottleneck-reworked#balance-work-the-way-the-test-runner-sees-it)

#### OpenAI: Split long test jobs into balanced shards

Codex split its single Windows Bazel test job into four shard jobs behind one aggregate check. A later change assigns targets by estimated duration, longest first, to the least-loaded shard.

Evidence: Single-run demonstration

Windows tests: one sharded run in about 7m29s, against a recent single-job median around 22m26s ([Source](https://github.com/openai/codex/pull/22408))

Use it when: A test job dominates the critical path, or existing shards finish at very different times.

Before applying it: Balance shard duration and total runner minutes together. A checked-in timing table goes stale as new tests arrive, so refresh it or use the runner's own timing data.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#balanced-shards)

[Primary source](https://github.com/openai/codex/pull/22408)

#### Ramp: Pack tests onto workers so they finish together

With the suite running in parallel pytest-xdist workers, Ramp saw the workers finish at different times, so the slowest worker set the job's length. The team wrote a more effective packing algorithm, with ChatGPT's help, as a proof of concept on a fork of pytest.

Evidence: Company-reported result or observation

The more effective packing algorithm saved 2 to 3 minutes. ([Source](https://builders.ramp.com/post/faster-python-tests-part-two))

Use it when: A test job runs in parallel workers or matrix shards, and the last one regularly finishes well after the first.

Before applying it: A packing algorithm that uses test timings needs them recorded; refresh them as tests are added so new slow tests spread across the workers.

[Read more in the Ramp playbook](https://starsling.dev/ci-playbooks/ramp#pack-tests-by-duration)

[Primary source](https://builders.ramp.com/post/faster-python-tests-part-two)

#### Stripe: Balance shards and keep a catch-all for new tests

Stripe iOS balanced PaymentSheet UI tests across four jobs: three with explicit assignments and a fourth that runs everything else, including new tests. A validator rejects duplicate assignments and stale exclusions.

Evidence: Source PR/commit evidence

In Stripe’s combined CI runs, the slowest PaymentSheet UI job went from about 26 to about 12 minutes, with worker and machine changes in the same PR. ([Source](https://github.com/stripe/stripe-ios/pull/7333))

Use it when: One shard sets the finish time while others wait idle.

Before applying it: Keep the union of test IDs identical and track new, renamed and deleted tests.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#rebalance-and-cover-shards)

[Primary source](https://github.com/stripe/stripe-ios/pull/7333)

### [Match test concurrency to the runner](https://starsling.dev/ci-playbooks/techniques/tune-test-workers)

Measure worker counts against available CPU and memory. More workers help only while the runner has capacity.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#tune-worker-width)**: Use it when A fixed worker count has never been measured, or workers contend for memory, physical cores or a shared host. Check before adopting: Nested worker processes and other virtual machines on the same host count toward the width.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#test-workers)**: Use it when Parallel tests and build output compete for limited runner memory. Check before adopting: Preserve the separate build heap requirement and measure peak memory, elapsed time and failures across worker counts.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#measured-concurrency)**: Use it when Test parallelism is a default or a guess for this runner. Check before adopting: More parallel workers can increase memory pressure and contention. Compare the same workload and runner, including reliability and total minutes.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#measure-worker-count)**: Use it when Tests run serially, or worker counts and runner sizes were chosen without measurement. Check before adopting: Isolate shared accounts and ports before adding workers, and keep tests that verify serial behavior in a serial lane. Measure each change separately; the source PRs changed workers and machines together.

#### Anthropic: Set test worker width from the actual host

Bun removed the CPU and thread caps that held its Linux test agents to two usable cores, and sized the parallel test width from the available cores. A follow-up capped the width at four on macOS, where each M2 Ultra host runs two tart virtual machines and every coordinator forks its own workers.

Evidence: Source PR/commit evidence

Use it when: A fixed worker count has never been measured, or workers contend for memory, physical cores or a shared host.

Before applying it: Nested worker processes and other virtual machines on the same host count toward the width.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#tune-worker-width)

[Primary source](https://github.com/oven-sh/bun/pull/36175)

#### Mastra: Fit test concurrency to the runner’s memory

The memory integration configuration capped Vitest threads at two after the source reported runner disconnects and memory pressure.

Evidence: Source PR/commit evidence

The source reports 61 failures in 771 runs. ([Source](https://github.com/mastra-ai/mastra/pull/13937))

The current Vitest configuration expresses the limit as maxWorkers: 2. ([Source](https://github.com/mastra-ai/mastra/blob/f92e947a448a47515acf14df1eddc5fd5c36f424/packages/memory/integration-tests/vitest.config.ts))

Use it when: Parallel tests and build output compete for limited runner memory.

Before applying it: Preserve the separate build heap requirement and measure peak memory, elapsed time and failures across worker counts.

[Read more in the Mastra playbook](https://starsling.dev/ci-playbooks/mastra#test-workers)

[Primary source](https://github.com/mastra-ai/mastra/pull/13937)

#### OpenAI: Measure how many tests to run at once

Codex compared Windows Bazel shards at 4, 8 and 12 local test jobs in a forced-test-execution experiment, then set the CI default to 8 with one .bazelrc line. Sharding spreads work across runners; this tunes how much runs at once on each runner.

Evidence: Source PR/commit evidence

Slowest shard: 835.5s to 609.3s at 8 local test jobs, in the experiment the PR cites ([Source](https://github.com/openai/codex/pull/31352))

Use it when: Test parallelism is a default or a guess for this runner.

Before applying it: More parallel workers can increase memory pressure and contention. Compare the same workload and runner, including reliability and total minutes.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#measured-concurrency)

[Primary source](https://github.com/openai/codex/pull/31352)

#### Stripe: Measure workers against the runner

Stripe iOS tried six simulator workers and kept four, which was faster and more reliable, and split Crypto UI tests into separate classes so Xcode could run them in parallel. Stripe Android sized individual device-heavy lanes and raised its managed-device ceiling to match its six shards. Metronome, a Stripe company, moved a publishing job whose doc build was timing out to an 8-core runner, on a suggestion from the Stainless team.

Evidence: Source PR/commit evidence

In the same combined runs, the Crypto UI test step went from about 10 to about 6 minutes. ([Source](https://github.com/stripe/stripe-ios/pull/7333))

Use it when: Tests run serially, or worker counts and runner sizes were chosen without measurement.

Before applying it: Isolate shared accounts and ports before adding workers, and keep tests that verify serial behavior in a serial lane. Measure each change separately; the source PRs changed workers and machines together.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#measure-worker-count)

[Primary source](https://github.com/stripe/stripe-ios/pull/7333)

### [Reduce repeated process and build-graph startup](https://starsling.dev/ci-playbooks/techniques/reduce-test-process-overhead)

Batch compatible test work or share its build graph to avoid paying process startup and initialization for every file or target.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#batch-test-processes)**: Use it when Process startup for each test file takes a large share of a big suite, and a reviewed set of files shares no mutable state. Check before adopting: Files in unlisted directories stay on the per-file path, and a new test file in a listed directory joins the batch; the batch reports every file it runs, and any error in the batch turns the job red.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#fewer-processes)**: Use it when A suite repeatedly starts interpreters, CLIs or separate test binaries. Check before adopting: Sharing a process can expose leaked state and environment changes. Keep tests isolated and compare results as well as runtime.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#share-framework-build-graph)**: Use it when Separate invocations rebuild shared dependencies or repeat test-runner startup. Check before adopting: Keep separate lanes for targets whose environment, test plans or process isolation differ.

#### Anthropic: Run reliable test files in one parallel invocation per shard

Each Bun test shard now runs its fast, historically reliable test files as one bun test --parallel batch, an invocation that distributes the files across a pool of worker processes, where each file used to start its own process. Files that have shown batch sensitivity go on a denylist and run on their own, and so do memory-measuring tests.

Evidence: Source PR/commit evidence

Per-lane test shard time for the whole change (batching, the full core count on test agents and one up-front addon build per shard), one main build against one branch build: debian x64-asan 8.5 to 4.9 minutes on average (42% lower), windows 2019 x64 8.6 to 6.0 minutes (30%), darwin 26 aarch64 15.5 to 12.6 minutes (19%). ([Source](https://github.com/oven-sh/bun/pull/36175))

Use it when: Process startup for each test file takes a large share of a big suite, and a reviewed set of files shares no mutable state.

Before applying it: Files in unlisted directories stay on the per-file path, and a new test file in a listed directory joins the batch; the batch reports every file it runs, and any error in the batch turns the job red.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#batch-test-processes)

[Primary source](https://github.com/oven-sh/bun/pull/36175)

#### OpenAI: Run tests in fewer processes

Codex moved every integration test under tests/ into one suite, so cargo builds and starts a single test binary. The Python SDK compiles its dependency-policy checker once per pytest worker and runs each of its policy cases in-process with fresh globals, keeping real subprocess tests for CLI parity.

Evidence: Source PR/commit evidence

Python 3.14 test job: 7m00s to 2m41s (2m24s before the policy matrix landed) ([Source](https://github.com/openai/openai-python/pull/3830))

Use it when: A suite repeatedly starts interpreters, CLIs or separate test binaries.

Before applying it: Sharing a process can expose leaked state and environment changes. Keep tests isolated and compare results as well as runtime.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#fewer-processes)

[Primary source](https://github.com/openai/openai-python/pull/3830)

#### Stripe: Build related test targets in one graph

Stripe iOS replaced many separate framework-test invocations with a combined FrameworkTests-CI scheme, split into PaymentSheet and non-PaymentSheet jobs. Targets with distinct package graphs keep their own paths.

Evidence: Source PR/commit evidence

Use it when: Separate invocations rebuild shared dependencies or repeat test-runner startup.

Before applying it: Keep separate lanes for targets whose environment, test plans or process isolation differ.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#share-framework-build-graph)

[Primary source](https://github.com/stripe/stripe-ios/pull/7332)

### [Run independent test packages concurrently](https://starsling.dev/ci-playbooks/techniques/parallel-test-packages)

Let independent packages or test environments run at the same time instead of invoking each one serially.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#parallel-crates)**: Use it when A serial test driver runs independent packages, crates or modules one after another on a multi-core runner. Check before adopting: Each process keeps its own log, and the job reports every failing package.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#pytest-xdist)**: Use it when One test environment takes most of the CI time and runs in a single process on a multi-core runner. Check before adopting: Tests that share files, ports or global state need per-worker isolation; grouping by file keeps a file's tests together.
- **[Uber](https://starsling.dev/ci-playbooks/uber#parallel-package-tests)**: Use it when A shell loop runs independent packages one at a time even though the language test runner schedules packages in parallel. Check before adopting: Remove shared-resource dependencies first, keep special-case tests, race settings and combined coverage, and measure CPU and memory at several concurrency levels.

#### Anthropic: Run independent test packages concurrently

Bun's Miri script builds every crate once, then runs one cargo miri test per crate, as many at a time as the runner has cores, longest crates first. It waits for all of them and exits nonzero if any crate reports an error.

Evidence: Estimate

The PR expects the Miri job to go from about 14.5 minutes to about 4 to 5 minutes (estimate), with the concurrent crates and a smaller Miri-sized tracker test together. ([Source](https://github.com/oven-sh/bun/pull/39218))

Use it when: A serial test driver runs independent packages, crates or modules one after another on a multi-core runner.

Before applying it: Each process keeps its own log, and the job reports every failing package.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#parallel-crates)

[Primary source](https://github.com/oven-sh/bun/pull/39218)

#### Spotify: Run the slowest test environment in parallel workers

This is a community contribution to Spotify's luigi project. Luigi's core test environment took about five minutes while the others took about a minute or less. The change adds pytest-xdist and runs the core tests with -n auto --dist=loadfile, which spreads test files across one worker per CPU and keeps each file's tests on one worker.

Evidence: Source PR/commit evidence

The author reports the core workflows running in 2 to 4 minutes, 1 to 2 minutes faster. ([Source](https://github.com/spotify/luigi/pull/3398))

Use it when: One test environment takes most of the CI time and runs in a single process on a multi-core runner.

Before applying it: Tests that share files, ports or global state need per-worker isolation; grouping by file keeps a file's tests together.

[Read more in the Spotify playbook](https://starsling.dev/ci-playbooks/spotify#pytest-xdist)

[Primary source](https://github.com/spotify/luigi/pull/3398)

#### Uber: Let the test runner schedule independent packages

After isolating its unit suite, Cadence replaced a shell loop that ran go test once per package with one invocation over the package list, so Go schedules independent packages concurrently and writes one coverage profile. OPT88's serial-suite diagnosis is adapted.

Evidence: Source PR/commit evidence

The PR reports unit and integration test runs and builds on the dependency isolation in cadence-workflow/cadence#6800. ([Source](https://github.com/cadence-workflow/cadence/pull/6801))

Use it when: A shell loop runs independent packages one at a time even though the language test runner schedules packages in parallel.

Before applying it: Remove shared-resource dependencies first, keep special-case tests, race settings and combined coverage, and measure CPU and memory at several concurrency levels.

[Read more in the Uber playbook](https://starsling.dev/ci-playbooks/uber#parallel-package-tests)

[Primary source](https://github.com/cadence-workflow/cadence/pull/6801)

### [Keep unit tests independent of slow services](https://starsling.dev/ci-playbooks/techniques/mock-external-dependencies)

Use controlled substitutes for external dependencies when the test is intended to verify local behavior, keeping integration coverage separately.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[OpenAI](https://starsling.dev/ci-playbooks/openai#mock-dependencies)**: Use it when A slow test calls a real dependency outside the code it exists to verify. Check before adopting: Keep one job that exercises the real dependency, nightly or after merge. A deterministic mock proves the code under test, and the live job proves the service.
- **[Uber](https://starsling.dev/ci-playbooks/uber#separate-unit-dependencies)**: Use it when Unit or component checks wait for database servers whose engine-specific behavior they never exercise. Check before adopting: Keep real-engine integration tests and compare their discovered test sets and combined coverage, since unit-only coverage shifts when tests move. The server-startup test also drops a Cassandra schema-version assertion; keep an equivalent engine-specific check in the integration lane.

#### OpenAI: Mock the slow dependency

OpenAI added a missing mock to a 63-test resolver suite and kept all 63 tests. In the public evals repository, the workflow validates new evaluations against dummy chat and completion models, so the check runs without a live model call.

Evidence: Company-reported result or observation

63-test resolver suite: 592.6s to 29.8s after adding the missing mock ([Source](https://developers.openai.com/blog/maintaining-software-with-the-gpt-6-family#other-ideas-to-try))

Use it when: A slow test calls a real dependency outside the code it exists to verify.

Before applying it: Keep one job that exercises the real dependency, nightly or after merge. A deterministic mock proves the code under test, and the live job proves the service.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#mock-dependencies)

[Primary source](https://developers.openai.com/blog/maintaining-software-with-the-gpt-6-family#other-ideas-to-try)

#### Uber: Keep unit tests independent of database servers

Cadence removed Cassandra, MySQL, PostgreSQL and MongoDB services from its unit-test container. Component tests that only needed persistence switched to SQLite, and database-specific persistence and CLI tests moved into the integration tree, whose Makefile gained an explicit discovery pass. OPT87's boundary-isolation recipe is adapted with a local SQLite store.

Evidence: Source PR/commit evidence

The PR reports unit and integration test runs and a comparison of coverage reports. ([Source](https://github.com/cadence-workflow/cadence/pull/6800))

Use it when: Unit or component checks wait for database servers whose engine-specific behavior they never exercise.

Before applying it: Keep real-engine integration tests and compare their discovered test sets and combined coverage, since unit-only coverage shifts when tests move. The server-startup test also drops a Cassandra schema-version assertion; keep an equivalent engine-specific check in the integration lane.

[Read more in the Uber playbook](https://starsling.dev/ci-playbooks/uber#separate-unit-dependencies)

[Primary source](https://github.com/cadence-workflow/cadence/pull/6800)

### [Shorten deliberate test waits](https://starsling.dev/ci-playbooks/techniques/shorten-proven-test-waits)

Reduce waits whose timing requirements are known, preserving the timeout or consistency behavior the test is intended to cover.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#shorten-deliberate-sleeps)**: Use it when A test or a CI-run example sleeps for a fixed time only to exercise a timeout or a slow path. Check before adopting: The sleep and the timeout it triggers shrink together, so the timeout branch still runs and is asserted.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#shorter-waits)**: Use it when A service exposes no usable readiness predicate and repeated conservative waits dominate tests. Check before adopting: Prefer a real completion signal; retain coverage and stress slow runners before choosing a lower bound.

#### Anthropic: Shorten deliberate sleeps in tests and examples

The Claude Agent SDK's error-handling example, which CI runs on every pull request, asked for a 60-second sleep to trigger a 10-second timeout. The change asks for 5 seconds and times out at 3, so the timeout path still runs.

Evidence: Estimate

The PR's estimate is that the shorter sleep removes 50+ seconds from the examples job; the same PR also raises that step's timeout. ([Source](https://github.com/anthropics/claude-agent-sdk-python/pull/760))

Use it when: A test or a CI-run example sleeps for a fixed time only to exercise a timeout or a slow path.

Before applying it: The sleep and the timeout it triggers shrink together, so the timeout branch still runs and is asserted.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#shorten-deliberate-sleeps)

[Primary source](https://github.com/anthropics/claude-agent-sdk-python/pull/760)

#### Mastra: Shorten only waits whose consistency is already proved

Chroma reduced 80 fixed 2-second waits, from three call sites, to 200 milliseconds; Couchbase reduced per-operation waits while adding a service readiness probe.

Evidence: Estimate

Chroma’s PR estimates the Run Chroma tests step drops from 160 seconds of sleeping to about 16 seconds. ([Source](https://github.com/mastra-ai/mastra/pull/13866))

Use it when: A service exposes no usable readiness predicate and repeated conservative waits dominate tests.

Before applying it: Prefer a real completion signal; retain coverage and stress slow runners before choosing a lower bound.

[Read more in the Mastra playbook](https://starsling.dev/ci-playbooks/mastra#shorter-waits)

[Primary source](https://github.com/mastra-ai/mastra/pull/13866)

### [Wait for observable readiness instead of sleeping](https://starsling.dev/ci-playbooks/techniques/poll-readiness)

Poll the service or state the test actually needs, with a bounded timeout and useful failure output.

#### How to choose an approach

Compare the examples by repository setup, prerequisites, and risks before adopting one. Each company implemented this in its own environment; measure each change against your own baseline.

- **[Mastra](https://starsling.dev/ci-playbooks/mastra#poll-indexing)**: Use it when Repeated waits dominate an eventual-consistency integration suite. Check before adopting: Validate content or version readiness and fail clearly on a deadline; a matching count alone can miss same-size updates.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#wait-for-observable-state)**: Use it when Tests sleep for a fixed period before checking an asynchronous result. Check before adopting: Probe the capability the next step needs; an open port can come before migrations or workers are ready. Keep tests that intentionally verify timeouts unchanged.

#### Mastra: Poll the condition the test actually needs

MongoDB vector tests replaced fixed sleeps with polling. Couchbase used its Search service ping for startup.

Evidence: Source PR/commit evidence

Use it when: Repeated waits dominate an eventual-consistency integration suite.

Before applying it: Validate content or version readiness and fail clearly on a deadline; a matching count alone can miss same-size updates.

[Read more in the Mastra playbook](https://starsling.dev/ci-playbooks/mastra#poll-indexing)

[Primary source](https://github.com/mastra-ai/mastra/pull/13808)

#### Stripe: Wait for readiness with a bounded poll

Stripe sync-engine replaced fixed sleeps with a waitFor helper that polls a condition, returns as soon as it holds and throws when the deadline passes. Stripe Android’s UI test waits for a snackbar to hide only when it overlaps the element it needs to tap.

Evidence: Source PR/commit evidence

Use it when: Tests sleep for a fixed period before checking an asynchronous result.

Before applying it: Probe the capability the next step needs; an open port can come before migrations or workers are ready. Keep tests that intentionally verify timeouts unchanged.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#wait-for-observable-state)

[Primary source](https://github.com/stripe/sync-engine-fork/pull/124)

### [Build the test database once for all workers](https://starsling.dev/ci-playbooks/techniques/coordinate-test-setup)

Have one parallel worker apply the schema while the others wait for it, so more workers stop repeating the same setup.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Ramp](https://starsling.dev/ci-playbooks/ramp#coordinate-test-setup)**: Use it when Tests run in parallel workers that each repeat the same database or fixture setup, and raising the worker count stops paying off. Check before adopting: The other workers wait for the one that builds the schema, so the setup itself still sets a floor on the job's time.

#### Ramp: Build the test database once for all workers

Ramp's test setup recreated the database from scratch by applying every migration, and each pytest-xdist worker ran that setup in parallel. At higher worker counts the concurrent schema changes ran Postgres out of working memory and repeated the same work. Ramp restructured the setup so one worker applies the schema, which let it run more workers on a machine twice the size.

Evidence: Company-reported result or observation

Coordinating the schema setup let Ramp double the machine size and halve CI test times, which had been creeping up towards 20 minutes. ([Source](https://builders.ramp.com/post/faster-python-tests))

Use it when: Tests run in parallel workers that each repeat the same database or fixture setup, and raising the worker count stops paying off.

Before applying it: The other workers wait for the one that builds the schema, so the setup itself still sets a floor on the job's time.

[Read more in the Ramp playbook](https://starsling.dev/ci-playbooks/ramp#coordinate-test-setup)

[Primary source](https://builders.ramp.com/post/faster-python-tests)

### [Complete the fixture before its timeout fires](https://starsling.dev/ci-playbooks/techniques/finish-fixtures-before-timeout)

Exercise the event that advances the test, so it stops waiting on the timer that rescues it.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Uber](https://starsling.dev/ci-playbooks/uber#finish-fixtures-before-timeout)**: Use it when Tests regularly spend a fixed interval waiting for an operation the fixture could complete immediately. Check before adopting: Confirm the test is about the completion path, keep dedicated timeout tests, and update event histories when completion adds events.

#### Uber: Complete the fixture before its timeout fires

Cadence integration tests polled the foreign workflow's activity, so the next decision waited for the original activity to time out. The fix completes the original workflow's activity and updates expected event IDs for the extra ActivityStarted event. OPT19 is adapted to an accidental activity timeout.

Evidence: Source PR/commit evidence

The author identifies a 10-second wait in each affected test. ([Source](https://github.com/cadence-workflow/cadence/pull/8290))

Use it when: Tests regularly spend a fixed interval waiting for an operation the fixture could complete immediately.

Before applying it: Confirm the test is about the completion path, keep dedicated timeout tests, and update event histories when completion adds events.

[Read more in the Uber playbook](https://starsling.dev/ci-playbooks/uber#finish-fixtures-before-timeout)

[Primary source](https://github.com/cadence-workflow/cadence/pull/8290)

### [Let an agent break import cycles and make the app load lazily](https://starsling.dev/ci-playbooks/techniques/agent-import-cycles)

Point a coding agent at the import cycles and eager imports behind a slow app boot, and merge what it lands.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Ramp](https://starsling.dev/ci-playbooks/ramp#agent-import-cycles)**: Use it when Tests and tools start the application, and its startup imports far more modules than a single test or command uses. Check before adopting: Lazy imports move a failure from startup to the first use, so keep a test that imports every module.

#### Ramp: Let an agent break import cycles and make the app load lazily

Ramp's engineer in the August 2026 talk described telling the agent to fix all the app's import cycles and to make the app lazy, because the app boots up an enormous amount of Python modules. The agent made a lot of progress on both, and a lot of that code was merged. The talk presents this among the hard problems Ramp gave the agent.

Evidence: Company-reported result or observation

2026: Ramp reported that a lot of the agent's import-cycle and lazy-loading code was merged. ([Source](https://claude.com/customers/ramp-boris-office-hours))

Use it when: Tests and tools start the application, and its startup imports far more modules than a single test or command uses.

Before applying it: Lazy imports move a failure from startup to the first use, so keep a test that imports every module.

[Read more in the Ramp playbook](https://starsling.dev/ci-playbooks/ramp#agent-import-cycles)

[Primary source](https://claude.com/customers/ramp-boris-office-hours)

### [Move unit tests to a parallel-by-default framework, gated on ten clean runs](https://starsling.dev/ci-playbooks/techniques/parallel-test-framework)

Migrate serial tests to a framework that runs them in parallel, and ship each package after ten green runs.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[DoorDash](https://starsling.dev/ci-playbooks/doordash#parallel-test-framework)**: Use it when A test step runs mostly serially, and the test framework or a newer one can run the same tests in parallel. Check before adopting: Parallel runs expose hidden order, shared-state and timing assumptions; fix each root cause, and leave tests on the old framework where the new one lacks a needed feature.

#### DoorDash: Move unit tests to a parallel-by-default framework, gated on ten clean runs

Mostly serialized XCTest runs had stretched DoorDash's iOS CI test step into double-digit minutes. DoorDash migrated its shared iOS packages to Swift Testing, which runs synchronous and asynchronous tests in parallel by default. A Cursor rule made the mechanical edits, and the migration prompt told the agent to run each package's tests with Bazel after every file and fix failures before moving on. A package counted as migrated once its tests passed ten times in a row, which surfaced tests that depended on order, shared state or timing. The unit test step in CI got about 60% faster, and overall builds for shared iOS packages about 40% faster.

Evidence: Company-reported result or observation

The unit test step in CI became roughly 60% faster. ([Source](https://careersatdoordash.com/blog/doordash-using-cursor-to-accelerate-migration-to-swift-testing/))

This contributed to an average of about 40% faster overall builds for shared iOS packages. ([Source](https://careersatdoordash.com/blog/doordash-using-cursor-to-accelerate-migration-to-swift-testing/))

Use it when: A test step runs mostly serially, and the test framework or a newer one can run the same tests in parallel.

Before applying it: Parallel runs expose hidden order, shared-state and timing assumptions; fix each root cause, and leave tests on the old framework where the new one lacks a needed feature.

[Read more in the DoorDash playbook](https://starsling.dev/ci-playbooks/doordash#parallel-test-framework)

[Primary source](https://careersatdoordash.com/blog/doordash-using-cursor-to-accelerate-migration-to-swift-testing/)

### [Reduce the setup floor before increasing shard count](https://starsling.dev/ci-playbooks/techniques/setup-cost-limits-sharding)

Each extra shard repeats checkout, dependencies and container setup.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Linear](https://starsling.dev/ci-playbooks/linear#setup-cost-limits-sharding)**: Use it when More shards would repeat substantial fixed setup work. Check before adopting: Remeasure per-shard setup before changing shard count.

#### Linear: Reduce the setup floor before increasing shard count

Each extra shard repeats checkout, dependencies and container setup. Before its setup work, Linear’s test job used 4 shards and spent 8.3 minutes on setup; afterward it ran 8 shards with 7.5 minutes of setup.

Evidence: Documented technique

Use it when: More shards would repeat substantial fixed setup work.

Before applying it: Remeasure per-shard setup before changing shard count.

[Read more in the Linear playbook](https://starsling.dev/ci-playbooks/linear#setup-cost-limits-sharding)

[Primary source](https://linear.app/now/ci-bottleneck-reworked#sharding-is-limited-by-setup-overhead)

### [Share module initialization only for explicitly safe tests](https://starsling.dev/ci-playbooks/techniques/share-module-state)

Linear added an opt-in Vitest project with isolate: false, so safe files share a module registry within each worker.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Linear](https://starsling.dev/ci-playbooks/linear#share-module-state)**: Use it when Import and transform overhead dominate eligible Vitest files. Check before adopting: This was Linear’s highest correctness risk. Files that use fake timers or shared state stay in the isolated project.

#### Linear: Share module initialization only for explicitly safe tests

Linear added an opt-in Vitest project with isolate: false, so safe files share a module registry within each worker. Each eligible file carries an opt-in comment, shared state gets explicit teardown, and because agents now write most of Linear’s tests, its agent skills follow the same opt-in by default. Linear calls it its largest single performance improvement, worth roughly 17% in monthly savings.

Evidence: Documented technique

Use it when: Import and transform overhead dominate eligible Vitest files.

Before applying it: This was Linear’s highest correctness risk. Files that use fake timers or shared state stay in the isolated project.

[Read more in the Linear playbook](https://starsling.dev/ci-playbooks/linear#share-module-state)

[Primary source](https://linear.app/now/ci-bottleneck-reworked#share-module-state-only-with-strict-isolation-rules)

### [Split independent work off the longest job](https://starsling.dev/ci-playbooks/techniques/split-long-lanes)

Run independent suites in parallel jobs when the overlap saves more than the repeated setup costs.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Stripe](https://starsling.dev/ci-playbooks/stripe#split-long-lanes)**: Use it when Two independent suites run one after another in the job that holds up the pull request. Check before adopting: Each new job repeats setup and may queue. Keep every test variant and simulator version, and keep one stable required check that reports every job’s result.

#### Stripe: Split independent work off the longest job

Stripe iOS split installation and integration tests into separate jobs, keeping all five installation variants together so they share setup. Earlier, it moved one measured slow test class into its own job.

Evidence: Source PR/commit evidence

The combined job took about 27 minutes; after the split, installation took about 11 to 12 minutes and integration about 13 minutes, running in parallel, in Stripe’s combined CI runs. ([Source](https://github.com/stripe/stripe-ios/pull/7334))

Use it when: Two independent suites run one after another in the job that holds up the pull request.

Before applying it: Each new job repeats setup and may queue. Keep every test variant and simulator version, and keep one stable required check that reports every job’s result.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#split-long-lanes)

[Primary source](https://github.com/stripe/stripe-ios/pull/7334)

### [Test supported runtimes concurrently](https://starsling.dev/ci-playbooks/techniques/matrix-by-supported-runtime)

Give each target framework its own job that installs only what it needs.

#### How to choose an approach

This technique currently has one documented company example. Validate its prerequisites, tradeoffs and original CI environment against your repository before adapting it.

- **[Stripe](https://starsling.dev/ci-playbooks/stripe#matrix-by-supported-runtime)**: Use it when A multi-runtime suite runs sequentially or installs every toolchain in every lane. Check before adopting: Keep every supported runtime gated, let every diagnostic matrix leg run to completion, and update branch protection for the new job names.

#### Stripe: Test supported runtimes concurrently

Stripe .NET replaced one sequential build-and-test job with per-framework matrix jobs and a native Windows .NET Framework lane. Each lane installs the latest SDK plus only its target runtime, and example builds run in their own job.

Evidence: Source PR/commit evidence

Use it when: A multi-runtime suite runs sequentially or installs every toolchain in every lane.

Before applying it: Keep every supported runtime gated, let every diagnostic matrix leg run to completion, and update branch protection for the new job names.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#matrix-by-supported-runtime)

[Primary source](https://github.com/stripe/stripe-dotnet/pull/3340)

## CI optimization is continuous work.

Apply these techniques yourself, or use StarSling Runners for faster GitHub Actions and optional agent-driven optimization PRs, which teams on eligible paid plans can turn on.

[Get self-improving CI](https://starsling.dev/products/runners)

## Company Playbooks

### [Anthropic](https://starsling.dev/ci-playbooks/anthropic)

Run only affected tests

### [DoorDash](https://starsling.dev/ci-playbooks/doordash)

Run build and test in parallel, sharded across CI agents

### [Linear](https://starsling.dev/ci-playbooks/linear)

Benchmark the same job on faster hardware

### [Mastra](https://starsling.dev/ci-playbooks/mastra)

StarSling runners for the slowest test workflow

### [OpenAI](https://starsling.dev/ci-playbooks/openai)

Rank CI fixes by the minutes they save

### [Ramp](https://starsling.dev/ci-playbooks/ramp)

Move a CPU-bound test suite to a faster CPU

### [Shopify](https://starsling.dev/ci-playbooks/shopify)

Let an agent iterate against one CI metric

### [Spotify](https://starsling.dev/ci-playbooks/spotify)

Build arm64 on native arm64 runners

### [Stripe](https://starsling.dev/ci-playbooks/stripe)

Run only the tests a change can reach

### [Uber](https://starsling.dev/ci-playbooks/uber)

Build the merge-queue speculations most likely to be needed

## Related guides

Implementation guides for the approaches in this category.

### [Cut repeated test setup](https://starsling.dev/best-practices/github-actions/cut-repeated-test-setup)

Move shared fixture work out of individual test cases and keep necessary cleanup.

### [Shard tests across parallel jobs](https://starsling.dev/best-practices/github-actions/shard-tests)

Split a long suite with a matrix and your test framework’s sharding support.

### [Diagnose slow tests in CI](https://starsling.dev/github-actions/problems/slow-tests)

Check common causes in order and confirm each with workflow evidence.

### [Optimize Playwright tests](https://starsling.dev/github-actions/optimizations/optimize-playwright)

Choose trace and video retention and install the browsers your suite uses.

### [Make Vitest run mode explicit](https://starsling.dev/github-actions/optimizations/optimize-vitest)

Pin run mode to the test command and bound the job with a timeout.
