---
title: "Compute waste"
description: "Find CI work that consumes compute without helping the next result. Compare ways to bound, reuse, or remove that work."
url: https://starsling.dev/ci-playbooks/categories/cost
canonicalUrl: https://starsling.dev/ci-playbooks/categories/cost
---

# Compute waste

Find CI work that consumes compute without helping the next result. Compare ways to bound, reuse, or remove that work.

## Where to start

Diagnose first: are you paying for redundant jobs, excessive setup, idle wait, or a costly runner class? Compute cost depends on how much work runs, how long it runs, and the resources it occupies. Start with repeated or obsolete jobs, expensive setup, and workloads that exceed their useful timeout. Compare cost and time together after each change.

## Apply these techniques

````text
Evaluate this CI improvement area in my repository: Compute waste.

Find CI work that consumes compute without helping the next result. Compare ways to bound, reuse, or remove that work.

Read the techniques and source-backed company examples: https://starsling.dev/ci-playbooks/categories/cost.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

12 techniques with source-backed company examples.

### [Cancel superseded pull request runs](https://starsling.dev/ci-playbooks/techniques/cancel-superseded-runs)

Free runner capacity by canceling validation that a newer commit has replaced. Keep required main-branch and release runs intact.

#### How it works

Group validation runs by the pull request or branch they validate. When a newer commit starts another run in that group, cancel the older validation. Scope the cancellation rule to work that the new run replaces; deployment, release and migration workflows need their own completion rules.

#### 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#cancel-superseded-runs)**: Use it when Developers or agents push to the same branch several times while CI is still running. Check before adopting: Deploy, publish and migration workflows keep running to completion; only runs whose result a newer commit replaces are cancelled.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#cancel-superseded)**: Use it when Repeated pushes leave obsolete verification jobs occupying runners. Check before adopting: Key the group on workflow plus the logical PR or ref, with safe fallbacks, so a new commit cancels the old run. Keep release and destructive side-effect jobs out of indiscriminate cancellation.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#cancel-only-obsolete-validation)**: Use it when Superseded pull request runs keep consuming runners. Check before adopting: Scope cancellation to replaceable pull request validation. Releases, deployments, migrations and default-branch runs that must finish keep cancel-in-progress off.
- **[Uber](https://starsling.dev/ci-playbooks/uber#cancel-superseded-runs)**: Use it when Developers push several commits to a pull request in quick succession and each push starts a full run. Check before adopting: Key the group on the pull request so runs for different pull requests never cancel each other, and keep default-branch and release runs out of cancel-in-progress.

#### Anthropic: Cancel superseded runs, never on main

Anthropic's Python and TypeScript SDKs added a workflow concurrency group keyed on the branch, so a newer push cancels the older run. On main the group is keyed on the commit, so main runs never cancel each other.

Evidence: Source PR/commit evidence

Use it when: Developers or agents push to the same branch several times while CI is still running.

Before applying it: Deploy, publish and migration workflows keep running to completion; only runs whose result a newer commit replaces are cancelled.

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

[Primary source](https://github.com/anthropics/anthropic-sdk-python/commit/c8e75ece9e407966ed077a8e4e924e7ebd041c71)

#### Mastra: Cancel obsolete runs using the correct event identity

Mastra added workflow concurrency and cancellation to lint, docs and workspace workflows. Related changes handled workflow_run identity and separated comment-driven approval updates from PR updates.

Evidence: Source PR/commit evidence

Use it when: Repeated pushes leave obsolete verification jobs occupying runners.

Before applying it: Key the group on workflow plus the logical PR or ref, with safe fallbacks, so a new commit cancels the old run. Keep release and destructive side-effect jobs out of indiscriminate cancellation.

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

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

#### Stripe: Cancel superseded pull request runs

Stripe iOS added a concurrency group to its dead-code workflow, keyed by pull request number, with cancel-in-progress so a new push stops the previous scan.

Evidence: Source PR/commit evidence

Use it when: Superseded pull request runs keep consuming runners.

Before applying it: Scope cancellation to replaceable pull request validation. Releases, deployments, migrations and default-branch runs that must finish keep cancel-in-progress off.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#cancel-only-obsolete-validation)

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

#### Uber: Cancel superseded pull request runs

Base Web added a concurrency group keyed on the workflow and pull request number to its PR Check workflow, with cancel-in-progress, so a new push stops the run it supersedes. NilAway did the same for its Golden Test workflow.

Evidence: Source PR/commit evidence

Use it when: Developers push several commits to a pull request in quick succession and each push starts a full run.

Before applying it: Key the group on the pull request so runs for different pull requests never cancel each other, and keep default-branch and release runs out of cancel-in-progress.

[Read more in the Uber playbook](https://starsling.dev/ci-playbooks/uber#cancel-superseded-runs)

[Primary source](https://github.com/uber/baseweb/pull/5407)

### [Put time limits on jobs that can hang](https://starsling.dev/ci-playbooks/techniques/bound-job-runtime)

Set job timeouts from normal run history so stalled checks release runners and produce a failure that can be investigated.

#### 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#flaky-job-timeouts)**: Use it when A non-matrix integration job has timed-out runs near the 360-minute default and at least three successful runs to measure. Check before adopting: A timed-out required check still blocks the merge, so the hanging tests get fixed by name.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#bounded-jobs)**: Use it when A database, checkout or external service occasionally hangs beyond useful execution time. Check before adopting: A timeout caps the runner minutes a hung job can burn. Leave room for normal tails and diagnostics.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#bound-hung-tests)**: Use it when A job with no timeout-minutes can stall and hold a runner until the platform default. Check before adopting: Set the ceiling from the job's real duration with headroom for cold setup.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#job-timeouts)**: Use it when A job sometimes hangs and runs until the platform's default limit, six hours on GitHub-hosted runners. Check before adopting: Set each timeout well above the longest healthy run so slow runs still finish; the passing iOS run on this change took 6m34s against a 45-minute cap.

#### Anthropic: Bound flaky job runtime

Anthropic describes running tests that were already flaky or failing across the board. As a StarSling pattern for GitHub Actions, a timeout set from successful run history stops a hanging job from holding a runner for six hours.

Evidence: StarSling pattern for GitHub Actions

Use it when: A non-matrix integration job has timed-out runs near the 360-minute default and at least three successful runs to measure.

Before applying it: A timed-out required check still blocks the merge, so the hanging tests get fixed by name.

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

[Primary source](https://claude.com/resources/articles/agentic-coding-is-straining-ci-heres-how-we-scaled-test-impact-analysis-at-anthropic)

#### Mastra: Bound integration jobs that can hang

Mastra added explicit timeouts to integration and change-detection jobs, then broadened timeout coverage. Later checkout timeouts were lengthened, showing why the bound needs observed tail data.

Evidence: Source PR/commit evidence

The combined store workflow keeps its job timeouts in test-combined-stores.yml. ([Source](https://github.com/mastra-ai/mastra/blob/f92e947a448a47515acf14df1eddc5fd5c36f424/.github/workflows/test-combined-stores.yml))

Use it when: A database, checkout or external service occasionally hangs beyond useful execution time.

Before applying it: A timeout caps the runner minutes a hung job can burn. Leave room for normal tails and diagnostics.

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

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

#### Shopify: Bound a test job that can hang

After tests looped indefinitely during development, Job Iteration added a five-minute timeout to a build matrix that normally runs in under a minute. A hung leg now stops at five minutes; the platform default is 360.

Evidence: Source PR/commit evidence

Merged 2026-06-04; the timeout is on the default branch. ([Source](https://github.com/Shopify/job-iteration/pull/717))

Use it when: A job with no timeout-minutes can stall and hold a runner until the platform default.

Before applying it: Set the ceiling from the job's real duration with headroom for cold setup.

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

[Primary source](https://github.com/Shopify/job-iteration/pull/717)

#### Spotify: Cap hung test jobs with timeouts

The confidence-sdk-flutter iOS test job could hang on the simulator and run for six hours. The change adds a 45-minute job timeout and a 30-minute timeout on the integration test step, boots the selected simulator before the tests and removes the blocking simctl bootstatus wait.

Evidence: Source PR/commit evidence

Use it when: A job sometimes hangs and runs until the platform's default limit, six hours on GitHub-hosted runners.

Before applying it: Set each timeout well above the longest healthy run so slow runs still finish; the passing iOS run on this change took 6m34s against a 45-minute cap.

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

[Primary source](https://github.com/spotify/confidence-sdk-flutter/pull/72)

### [Remove repeated build steps](https://starsling.dev/ci-playbooks/techniques/avoid-duplicate-builds)

Trace artifact producers and consumers so package, release, or test stages reuse work instead of triggering the same build again.

#### 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#release-build-once)**: Use it when A release job builds twice around version or manifest changes. Check before adopting: Keep the build that produces published artifacts after all version changes.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#avoid-lifecycle-rebuilds)**: Use it when A package lifecycle hook repeats a build in the same job. Check before adopting: Remove the explicit build only when the exact packaging command runs the hook. Disabled hooks or a changed lifecycle would ship a stale package.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#docker-stage-reuse)**: Use it when A multi-stage Dockerfile or build compiles the same source separately for its build, test and lint stages. Check before adopting: Test and lint now wait for the build stage. Components that need a different build profile keep their own base.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#drop-artifact-extraction)**: Use it when A job runs a second build only to copy outputs out or print a summary that nothing downstream reads. Check before adopting: Check that no later step, release or reviewer uses the exported files before removing the export.

#### Mastra: Build release artifacts after the final version change

Stable publishing removed an early build and kept one build after prerelease exit and installation. Earlier version-PR automation removed builds from the metadata-only versioning step.

Evidence: Source PR/commit evidence

Use it when: A release job builds twice around version or manifest changes.

Before applying it: Keep the build that produces published artifacts after all version changes.

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

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

#### OpenAI: Let the package lifecycle build once

Codex Security removed the explicit build steps before pnpm pack, because the package's prepack script already runs the build.

Evidence: Source PR/commit evidence

One build per packaging job, run by prepack ([Source](https://github.com/openai/codex-security/pull/397))

Use it when: A package lifecycle hook repeats a build in the same job.

Before applying it: Remove the explicit build only when the exact packaging command runs the hook. Disabled hooks or a changed lifecycle would ship a stale package.

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

[Primary source](https://github.com/openai/codex-security/pull/397)

#### Spotify: Let test and lint stages reuse the build stage

Confidence's Dockerfile ran each component's test and lint stages as siblings from the same base, so each stage compiled the source on its own. The change adds a build stage per component and derives the test and lint stages from it, letting cargo, Go and Maven reuse the compiled artifacts across six components.

Evidence: Source PR/commit evidence

In a local no-cache Docker build on an Apple M1 Max, total compute across the affected stages fell 61%, from 1807s to 712s. ([Source](https://github.com/spotify/confidence-resolver/pull/355))

Use it when: A multi-stage Dockerfile or build compiles the same source separately for its build, test and lint stages.

Before applying it: Test and lint now wait for the build stage. Components that need a different build profile keep their own base.

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

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

#### Spotify: Stop rebuilding just to export artifacts

After the cached Docker build and test step, every Confidence CI run ran a second docker build to export the WebAssembly artifact to the runner, then wrote a step summary listing the artifacts. The change removes both steps from the job.

Evidence: Source PR/commit evidence

Use it when: A job runs a second build only to copy outputs out or print a summary that nothing downstream reads.

Before applying it: Check that no later step, release or reviewer uses the exported files before removing the export.

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

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

### [Run CI once per pull request commit](https://starsling.dev/ci-playbooks/techniques/run-ci-once)

Remove overlapping push and pull request triggers so each change receives one intended validation run.

#### 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#one-run-per-change)**: Use it when Run history shows duplicate events for the same commit, or metadata events rerun checks that do not depend on that metadata. Check before adopting: Preview deployments, cache warming, release side effects and metadata-dependent checks keep the triggers they need.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#run-commit-once)**: Use it when The same commit runs equivalent validation through both push and pull_request, and developers push several times in a row. Check before adopting: Check that the two runs test the same ref with the same permissions before treating them as duplicates.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#push-main-only)**: Use it when A workflow triggers on both push and pull_request, and the push trigger covers every branch. Check before adopting: Branches without a pull request no longer get push builds; the workflow keeps workflow_dispatch for running them by hand.

#### Anthropic: Run CI once per change

Anthropic reports more CI jobs in a given day. As a StarSling pattern for GitHub Actions, removing duplicate push and pull_request runs and metadata-only triggers keeps each change to one run.

Evidence: StarSling pattern for GitHub Actions

Use it when: Run history shows duplicate events for the same commit, or metadata events rerun checks that do not depend on that metadata.

Before applying it: Preview deployments, cache warming, release side effects and metadata-dependent checks keep the triggers they need.

[Read more in the Anthropic playbook](https://starsling.dev/ci-playbooks/anthropic#one-run-per-change)

[Primary source](https://claude.com/resources/articles/agentic-coding-is-straining-ci-heres-how-we-scaled-test-impact-analysis-at-anthropic)

#### Shopify: Run each pull-request commit once

The Ruby extension's workflow ran on every push and every pull request, so each in-repo branch got two identical runs; for one commit they started three seconds apart. Shopify/vscode-shopify-ruby#808 limits push to main and adds a concurrency group whose cancellation applies only to pull_request events, so a main run that is already running is never cancelled, though a main run still waiting in the same group is replaced by a newer push.

Evidence: Source PR/commit evidence

Merged 2026-09-18; the workflow on the default branch is unchanged since. ([Source](https://github.com/Shopify/vscode-shopify-ruby/pull/808))

Use it when: The same commit runs equivalent validation through both push and pull_request, and developers push several times in a row.

Before applying it: Check that the two runs test the same ref with the same permissions before treating them as duplicates.

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

[Primary source](https://github.com/Shopify/vscode-shopify-ruby/pull/808)

#### Spotify: Run push builds only on the default branch

target-determinator's presubmit workflow ran on every push and on every pull request, so a commit pushed to a pull request branch in the repository ran the test matrix twice. The change restricts the push trigger to main; pull requests still run through the pull_request event.

Evidence: Source PR/commit evidence

Use it when: A workflow triggers on both push and pull_request, and the push trigger covers every branch.

Before applying it: Branches without a pull request no longer get push builds; the workflow keeps workflow_dispatch for running them by hand.

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

[Primary source](https://github.com/spotify/target-determinator/pull/15)

### [Bound agent execution and retries](https://starsling.dev/ci-playbooks/techniques/bounded-agent-execution)

Set explicit time, turn, or retry budgets so an agent that cannot finish returns control rather than consuming unbounded CI 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.

- **[DoorDash](https://starsling.dev/ci-playbooks/doordash#bounded-agent-timeouts)**: Use it when An agent or long-running step inside CI can loop without progress, and only a job-wide timeout stops it. Check before adopting: Set the soft limit from measured run times so normal runs finish untouched, and report a soft-stopped run as partial.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#agent-session-limits)**: Use it when An agent or bot job can iterate on its own, and a job-wide timeout is the only limit on a run. Check before adopting: Set the limits from your longest successful sessions. A task that needs more turns is a task to split into smaller changes. Adapted for GitHub Actions; compare the original system with yours.

#### DoorDash: Give every agent in a CI gate a soft and a hard timeout

One early problem in DoorDash's review agent: the same model request could repeat over and over without advancing its turn counter, so max-turn limits never fired, and a single overall timeout still allowed one stuck agent to burn 20 minutes of tokens. DoorDash moved to tighter per-agent timeouts. The soft timeout interrupts the agent and asks it to drop anything speculative and return only verified findings; the hard timeout is the final kill switch. DoorDash's feature-flag cleanup agents each run under a one-hour hard timeout.

Evidence: Documented technique

Use it when: An agent or long-running step inside CI can loop without progress, and only a job-wide timeout stops it.

Before applying it: Set the soft limit from measured run times so normal runs finish untouched, and report a soft-stopped run as partial.

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

[Primary source](https://careersatdoordash.com/blog/doordash-built-an-ai-code-reviewer-engineers-actually-listen-to/)

#### Spotify: Cap an agent's turns and session retries

**Translated to GitHub Actions**

- What Spotify built: Spotify's Fleet Management loop caps a session at 10 turns and three session retries.
- Your GitHub Actions version: Cap an agent's turns per session and its session retries.

Spotify's first agentic loop for Fleet Management gave the agent a prompt and the files in scope. The agent edited files over a few turns, took in feedback from the build system on each turn, and the task ended once the tests passed or the limits were exceeded: 10 turns per session and three session retries in total. Spotify's prompt guidance for migrations asks for one change at a time, so a session stays within its context window.

Evidence: Documented technique

Use it when: An agent or bot job can iterate on its own, and a job-wide timeout is the only limit on a run.

Before applying it: Set the limits from your longest successful sessions. A task that needs more turns is a task to split into smaller changes.

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

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

[Primary source](https://engineering.atspotify.com/2025/11/context-engineering-background-coding-agents-part-2)

### [Run shared validation once](https://starsling.dev/ci-playbooks/techniques/avoid-duplicate-validation)

Remove overlapping task or matrix invocations that perform the same check on the same inputs.

#### 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#version-hooks)**: Use it when An automated version PR repeats hooks already enforced by required CI. Check before adopting: Never bypass a check merely because it is expensive. Prove equivalent file scope and failure propagation in required workflows; retain checks that only the hook performs.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#delegate-snapshots-once)**: Use it when Snapshot or generated-output checks run both on their own and inside a broad suite. Check before adopting: Skip a class in one lane only while a required lane runs it, and keep runtime variants and aggregate results unchanged.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#remove-duplicate-tests)**: Use it when A test wrapper invokes another test task that CI also calls directly, or two configurations run the same code. Check before adopting: Compare the test and screenshot inventories before and after, and keep a configuration that has its own code paths or its own published build.

#### Mastra: Avoid repeating CI checks in a version bot’s commit hook

The versioning action disabled Husky because separate workflows already handled its precommit checks. This adapts the duplicate-command recipe and depends on required CI running the same checks.

Evidence: Source PR/commit evidence

Use it when: An automated version PR repeats hooks already enforced by required CI.

Before applying it: Never bypass a check merely because it is expensive. Prove equivalent file scope and failure propagation in required workflows; retain checks that only the hook performs.

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

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

#### Stripe: Run snapshot checks once in a dedicated lane

Stripe iOS re-records snapshots in a dedicated job and skips those snapshot classes in the main unit-test jobs only when that dedicated job is part of the pipeline. Standalone framework jobs keep every test.

Evidence: Source PR/commit evidence

Use it when: Snapshot or generated-output checks run both on their own and inside a broad suite.

Before applying it: Skip a class in one lane only while a required lane runs it, and keep runtime variants and aggregate results unchanged.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#delegate-snapshots-once)

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

#### Stripe: Stop invoking tests twice through overlapping tasks

Stripe Android’s screenshot verification already ran the unit tests. A new dispatcher task runs screenshot verification for modules that have it and unit tests for the rest, so each module runs its tests once. Stripe .NET removed a Debug build and test pass that repeated the Release pass for an SDK with no configuration-specific code.

Evidence: Source PR/commit evidence

Use it when: A test wrapper invokes another test task that CI also calls directly, or two configurations run the same code.

Before applying it: Compare the test and screenshot inventories before and after, and keep a configuration that has its own code paths or its own published build.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#remove-duplicate-tests)

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

### [Share setup across small checks](https://starsling.dev/ci-playbooks/techniques/share-small-job-setup)

Combine compatible short checks so checkout and dependency setup do not dominate the time spent doing useful work.

#### 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#batch-short-checks)**: Use it when Independent short jobs repeat more checkout and dependency setup than useful work. Check before adopting: Keep every check and its exit status, and keep the remaining critical path as short as before.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#share-small-job-setup)**: Use it when Independent small jobs spend more time on repeated setup than on their own work. Check before adopting: A combined job runs its parts in sequence, so compare the new critical path with the setup saved. Keep required-check names through an aggregator, and split again when a combined job becomes the long pole.

#### Linear: Pay setup twice instead of seven times

Linear consolidated seven small independent checks into two jobs and ran their tasks concurrently. Based on June usage, the change saved roughly 87,000 runner-minutes per month, equivalent to 11.8% of Linear’s total CI usage.

Evidence: Documented technique

Use it when: Independent short jobs repeat more checkout and dependency setup than useful work.

Before applying it: Keep every check and its exit status, and keep the remaining critical path as short as before.

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

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

#### Stripe: Share setup across compatible small checks

Stripe iOS combined several pairs of small jobs, including snapshot recording with the localization upload and the Connect and Crypto UI tests, so each pair pays for setup once.

Evidence: Source PR/commit evidence

Use it when: Independent small jobs spend more time on repeated setup than on their own work.

Before applying it: A combined job runs its parts in sequence, so compare the new critical path with the setup saved. Keep required-check names through an aggregator, and split again when a combined job becomes the long pole.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#share-small-job-setup)

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

### [Wait until a draft is ready for expensive checks](https://starsling.dev/ci-playbooks/techniques/defer-draft-work)

Avoid spending runner time on expensive validation until a draft pull request becomes ready for review.

#### 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.

- **[Shopify](https://starsling.dev/ci-playbooks/shopify#defer-draft-work)**: Use it when Developers push to draft pull requests many times before asking for review. Check before adopting: Without ready_for_review, a draft promoted to ready keeps its skipped result on an untested commit.
- **[Uber](https://starsling.dev/ci-playbooks/uber#draft-gating)**: Use it when Drafts receive frequent exploratory commits and your team agrees that expensive checks can wait. Check before adopting: Drafts get feedback later, so agree on it as a team policy. Test draft, ready, reopened, main and merge-queue events so every required check still reports.

#### Shopify: Run expensive checks when a draft becomes ready

Checkout Kit skips its change-detection job, and so every platform job behind it, while a pull request is a draft. Adding ready_for_review to the trigger types starts a full run as soon as the draft is marked ready.

Evidence: Source PR/commit evidence

Merged 2026-06-16; both lines are on the default branch. ([Source](https://github.com/Shopify/checkout-kit/pull/284))

Use it when: Developers push to draft pull requests many times before asking for review.

Before applying it: Without ready_for_review, a draft promoted to ready keeps its skipped result on an untested commit.

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

[Primary source](https://github.com/Shopify/checkout-kit/pull/284)

#### Uber: Wait until a draft is ready for expensive checks

SubmitQueue added draft guards to every CI job and its required aggregate, then added ready_for_review to the workflow events. Pushes to main and merge-queue runs keep their validation.

Evidence: Source PR/commit evidence

Drafts skip the full suite, and it runs when the pull request is marked ready for review. ([Source](https://github.com/uber/submitqueue/pull/180))

Use it when: Drafts receive frequent exploratory commits and your team agrees that expensive checks can wait.

Before applying it: Drafts get feedback later, so agree on it as a team policy. Test draft, ready, reopened, main and merge-queue events so every required check still reports.

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

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

### [Package once, and build full installers after merge](https://starsling.dev/ci-playbooks/techniques/package-once)

Drop the build step that packaging repeats, and build the unpacked app pull requests test.

#### 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#package-once)**: Use it when A job builds explicitly before a packaging or test command that builds again, or pull requests build release artifacts that no test reads. Check before adopting: Read the wrapper scripts before removing a build step. The full installers still need a post-merge run that someone watches.

#### DoorDash: Package once, and build full installers after merge

The desktop job ran a Build step and then packaging, which runs the same electron-vite build again. The PR removed the standalone Build step. Pull requests now package the unpacked app that the end-to-end journeys consume; pushes to main still build and inspect the full installers.

Evidence: Source PR/commit evidence

Use it when: A job builds explicitly before a packaging or test command that builds again, or pull requests build release artifacts that no test reads.

Before applying it: Read the wrapper scripts before removing a build step. The full installers still need a post-merge run that someone watches.

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

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

### [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)

### [Remove caches that cost more than they save](https://starsling.dev/ci-playbooks/techniques/remove-expensive-caches)

Measure cache restore and upload time against the work avoided. Remove caches whose transfer cost outweighs their benefit.

#### 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#drop-a-cache-that-costs-more)**: Use it when Comparable measurements show restore and save overhead outweighing the work avoided. Check before adopting: First try a package-manager store or a narrower cache, and separate miss-rate problems from an expensive successful restore.

#### Linear: Remove a cache when restoring it costs more than rebuilding

Linear measured a node_modules cache hit at about 28 seconds to restore, against roughly 7.5 seconds for a filtered install. The cache key depended on a frequently changing lockfile, so Linear removed the cache.

Evidence: Documented technique

Use it when: Comparable measurements show restore and save overhead outweighing the work avoided.

Before applying it: First try a package-manager store or a narrower cache, and separate miss-rate problems from an expensive successful restore.

[Read more in the Linear playbook](https://starsling.dev/ci-playbooks/linear#drop-a-cache-that-costs-more)

[Primary source](https://linear.app/now/ci-bottleneck-reworked#don%27t-cache-when-it%27s-faster-to-rebuild)

### [Stop writing caches that no later run restores](https://starsling.dev/ci-playbooks/techniques/disable-unused-caches)

Remove cache writes and archives that have no consumer, eliminating work without changing the build result.

#### 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#drop-unrestored-build-cache)**: Use it when A build tool writes a large local cache on every CI run, and no cache step saves or restores that directory. Check before adopting: Keep the cache wherever a later run restores it (developer machines, or CI with a cache step whose hit rate you have measured).

#### DoorDash: Turn off a build cache that CI never restores

Next.js caches Webpack, ESLint and SWC under .next/cache by default, and for large projects the Webpack cache can exceed 5 to 10 GB. CI jobs never saved that cache between runs, so every write was wasted disk activity. Disabling the Webpack disk cache in CI saved three minutes per run.

Evidence: Documented technique

Use it when: A build tool writes a large local cache on every CI run, and no cache step saves or restores that directory.

Before applying it: Keep the cache wherever a later run restores it (developer machines, or CI with a cache step whose hit rate you have measured).

[Read more in the DoorDash playbook](https://starsling.dev/ci-playbooks/doordash#drop-unrestored-build-cache)

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

## 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.

### [Bound runner jobs with timeouts](https://starsling.dev/best-practices/github-actions/bound-job-timeouts)

Set job and step timeouts using expected durations and failure behavior.

### [Right-size GitHub Actions runners](https://starsling.dev/best-practices/github-actions/right-size-runners)

Match runner resources to the workload and compare equivalent runs.
