---
title: "Unnecessary work"
description: "Remove work that a change does not need. Explore techniques for scoping workflows, avoiding duplicate runs, and cancelling obsolete work."
url: https://starsling.dev/ci-playbooks/categories/unnecessary
canonicalUrl: https://starsling.dev/ci-playbooks/categories/unnecessary
---

# Unnecessary work

Remove work that a change does not need. Explore techniques for scoping workflows, avoiding duplicate runs, and cancelling obsolete work.

## Where to start

Diagnose first: does the workflow run twice for one commit, run on unchanged paths, or repeat already verified work? Before making a job faster, check whether it needs to run. Use changed files, dependency relationships, and run state to identify avoidable work. Keep required checks reporting and preserve the release and deployment paths that still need a complete run.

## Apply these techniques

````text
Evaluate this CI improvement area in my repository: Unnecessary work.

Remove work that a change does not need. Explore techniques for scoping workflows, avoiding duplicate runs, and cancelling obsolete work.

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

18 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)

### [Fetch only the repository history a job needs](https://starsling.dev/ci-playbooks/techniques/fetch-needed-history)

Limit checkout depth and fetched refs to the comparisons or release history the job actually consumes.

#### 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#fetch-only-what-each-job-needs)**: Use it when A job uses only GitHub API metadata or a bounded subset of the repository. Check before adopting: Resolve merge bases, local actions, hashFiles and indirect scripts first. API-only jobs need an explicit repository context; history-dependent checks must retain enough history.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#shallow-checkout)**: Use it when Tests consume a source tree without history-based versioning or change detection. Check before adopting: Test tags and ancestry before giving release automation a shallow checkout.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#shallow-checkout)**: Use it when Checkout steps fetch full history or LFS files that the job never reads. Check before adopting: Jobs that compute versions, changelogs or diffs from Git history still need full history.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#fetch-needed-history)**: Use it when Checkout dominates CI, but lint or affected-target selection needs history. Check before adopting: Change detection needs a verified comparison base. Validate the PR head, the base and the merged tree, and run the full check whenever the base is ambiguous.

#### Linear: Give each checkout only the files and history it uses

Linear capped fetch depth, removed checkout from jobs that never used a working tree, and used sparse, blobless checkouts where it diffs paths. Capping fetch depth took the slowest change-detection gate from 94 to 20 seconds; removing checkout from jobs that never needed a working tree took them from 27 to 7 seconds.

Evidence: Company-reported result or observation

The median duration of the change-detection job fell from 26 to 8 seconds, p90 from 31 to 12 seconds, and the slowest run from 138 to 37 seconds. ([Source](https://linear.app/now/ci-bottleneck-reworked#fetch-only-what-each-job-needs))

Use it when: A job uses only GitHub API metadata or a bounded subset of the repository.

Before applying it: Resolve merge bases, local actions, hashFiles and indirect scripts first. API-only jobs need an explicit repository context; history-dependent checks must retain enough history.

[Read more in the Linear playbook](https://starsling.dev/ci-playbooks/linear#fetch-only-what-each-job-needs)

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

#### Mastra: Fetch only the release history the job consumes

The workspace test workflow removed fetch-depth: 0 while preserving the triggering workflow’s head SHA. Release publishing now fetches only main’s history, deepening it in batches without tags.

Evidence: Source PR/commit evidence

Release publishing deepens main’s history in batches of 50 commits, without tags. ([Source](https://github.com/mastra-ai/mastra/blob/f92e947a448a47515acf14df1eddc5fd5c36f424/.github/workflows/npm-publish.yml))

Use it when: Tests consume a source tree without history-based versioning or change detection.

Before applying it: Test tags and ancestry before giving release automation a shallow checkout.

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

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

#### OpenAI: Check out only the history the job reads

Four lint and test workflows in frontier-evals switched from a full-history checkout to a single commit, and three of them stopped downloading LFS files.

Evidence: Source PR/commit evidence

Four workflows check out one commit, and the three lint workflows stopped fetching LFS files ([Source](https://github.com/openai/frontier-evals/pull/53))

Use it when: Checkout steps fetch full history or LFS files that the job never reads.

Before applying it: Jobs that compute versions, changelogs or diffs from Git history still need full history.

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

[Primary source](https://github.com/openai/frontier-evals/pull/53)

#### Stripe: Fetch the comparison, not every branch

Stripe iOS replaced a fetch that could pull every branch with a shallow checkout of the PR head followed by progressively deeper fetches of only the PR and master histories until their merge base resolves.

Evidence: Source PR/commit evidence

Some jobs had spent up to 30+ minutes fetching history; in Stripe’s combined CI runs with the related changes, lint went from about 7 to about 6 minutes. ([Source](https://github.com/stripe/stripe-ios/pull/7331))

Use it when: Checkout dominates CI, but lint or affected-target selection needs history.

Before applying it: Change detection needs a verified comparison base. Validate the PR head, the base and the merged tree, and run the full check whenever the base is ambiguous.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#fetch-needed-history)

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

### [Skip jobs whose inputs have not changed](https://starsling.dev/ci-playbooks/techniques/skip-unaffected-jobs)

Gate expensive workflows and jobs on the files they consume, keeping required check behavior intact.

#### 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#path-filters)**: Use it when An expensive workflow runs for unrelated files, or app-specific jobs ignore the dependency graph. Check before adopting: Required checks stay reachable: a changes job with job-level conditions keeps a skipped job reporting success. Adapted for GitHub Actions; compare the original system with yours.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#affected-jobs)**: Use it when A workflow serves several independent packages or checks with distinct inputs. Check before adopting: Workflow files, shared config, dependency changes and routing-tool changes can affect every target. Keep required status reporting and treat invalid routing artifacts as full coverage.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#skip-docs-only)**: Use it when Documentation edits start expensive platform suites that build and test only source code. Check before adopting: A workflow skipped at the trigger never reports its required checks; filter at the job level so the checks still report.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#skip-unrelated-checks)**: Use it when Documentation or unrelated changes trigger an expensive, narrowly scoped check. Check before adopting: A whole-workflow path filter can leave a required check pending. An always-present gate that reports a no-work result keeps branch protection intact; include global build inputs in the scope.

#### Anthropic: Skip workflows a change cannot affect

**Translated to GitHub Actions**

- What Anthropic built: Anthropic runs a selected set of tests on each pull request.
- Your GitHub Actions version: Path and job-level filters that keep expensive workflows to changes in the files they depend on.

Anthropic runs a selected set of tests on each pull request. In GitHub Actions, path and job-level filters keep expensive workflows to changes in the files they depend on.

Evidence: Documented technique

Use it when: An expensive workflow runs for unrelated files, or app-specific jobs ignore the dependency graph.

Before applying it: Required checks stay reachable: a changes job with job-level conditions keeps a skipped job reporting success.

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

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

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

#### Mastra: Skip whole jobs only when their inputs are unchanged

Mastra gated AGENTS and peer-dependency validation by relevant inputs and generated workspace matrices from affected packages. Job-level conditions and validated matrices sit on top of trigger-level paths filters.

Evidence: Source PR/commit evidence

Use it when: A workflow serves several independent packages or checks with distinct inputs.

Before applying it: Workflow files, shared config, dependency changes and routing-tool changes can affect every target. Keep required status reporting and treat invalid routing artifacts as full coverage.

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

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

#### Shopify: Skip platform jobs for documentation-only changes

Checkout Kit keeps one shared filter file with per-platform filters that exclude Markdown files and docs directories, so a docs-only pull request starts none of the platform suites while workflow and filter changes still trigger validation. The same exclusions now live in .ci/changed-file-filters.yml on the default branch. Shopify CLI tried the workflow-level form and restored the tests, because they are required to merge.

Evidence: Source PR/commit evidence

Merged 2026-06-26; the exclusions are on the default branch. ([Source](https://github.com/Shopify/checkout-kit/pull/349))

Use it when: Documentation edits start expensive platform suites that build and test only source code.

Before applying it: A workflow skipped at the trigger never reports its required checks; filter at the job level so the checks still report.

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

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

#### Stripe: Gate expensive checks on their real inputs

Stripe iOS decides whether SDK or build inputs changed before installing tools and running API or dead-code analysis; build configuration counts as an input even inside example or test directories. Sorbet runs its editor-extension jobs on branches only when the extension changed, and always on master. Stripe React Native skipped its end-to-end workflow for Markdown-only pull requests.

Evidence: Source PR/commit evidence

Use it when: Documentation or unrelated changes trigger an expensive, narrowly scoped check.

Before applying it: A whole-workflow path filter can leave a required check pending. An always-present gate that reports a no-work result keeps branch protection intact; include global build inputs in the scope.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#skip-unrelated-checks)

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

### [Install and build only the workspace packages a job uses](https://starsling.dev/ci-playbooks/techniques/scope-workspace-builds)

Scope dependency installation and builds to the package or lane under test, including its actual dependencies.

#### 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#filtered-installs)**: Use it when A job’s dependency closure is smaller than the workspace. Check before adopting: Include transitive dependencies and generated prerequisites.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#scope-build)**: Use it when A narrow test job pays for unrelated workspace builds. Check before adopting: Trace transitive dependencies and generated artifacts. Before moving a build out of a gate, confirm another required check still runs it.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#scope-package-lanes)**: Use it when A monorepo runs one long sequential job that builds and tests every package on every run. Check before adopting: A new package has to be added to a lane, and each lane repeats install and setup.

#### Linear: Install or build only the workspace packages this step needs

Linear restricted its API test install to the API package and its dependencies, which cut pnpm install from 44-73 seconds to 16-18 seconds, then applied the same filter to API-adjacent jobs. In its public SDK, the release-preparation job builds only the package schema generation imports before updating the schema.

Evidence: Documented technique

Use it when: A job’s dependency closure is smaller than the workspace.

Before applying it: Include transitive dependencies and generated prerequisites.

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

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

#### Mastra: Build only the dependencies the test consumes

The memory test build replaced the entire stores wildcard with libsql, pg and upstash while retaining its other required packages. Another change moved a docs-server build out of the changed-test gate.

Evidence: Source PR/commit evidence

Use it when: A narrow test job pays for unrelated workspace builds.

Before applying it: Trace transitive dependencies and generated artifacts. Before moving a build out of a gate, confirm another required check still runs it.

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

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

#### Shopify: Give each CI lane a defined package set

The app libraries ran everything in ci.yml in sequence, once per Node version. Shopify/shopify-app-js#2777 split that into a lint job and three package-family jobs that run in parallel; each builds its own dependency closure with pnpm filters and tests only its family. The pattern match is partial: the filters scope each lane, and the speedup comes from running the lanes in parallel.

Evidence: Source PR/commit evidence

The PR reports CI running in about 5 minutes against about 11 before. ([Source](https://github.com/Shopify/shopify-app-js/pull/2777))

Use it when: A monorepo runs one long sequential job that builds and tests every package on every run.

Before applying it: A new package has to be added to a lane, and each lane repeats install and setup.

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

[Primary source](https://github.com/Shopify/shopify-app-js/pull/2777)

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

### [Build only what the test or analysis consumes](https://starsling.dev/ci-playbooks/techniques/build-only-consumed-inputs)

Declare the build dependencies of each validation lane so unrelated targets do not join its critical path.

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

- **[Stripe](https://starsling.dev/ci-playbooks/stripe#build-only-analysis-inputs)**: Use it when A scan builds artifacts, architectures or test bundles that it never analyzes. Check before adopting: The build the analysis reads stays. Keep all analyzed symbols, required architectures, compiler settings and index completeness, and rebuild when the scheme no longer matches what the scan expects.
- **[Uber](https://starsling.dev/ci-playbooks/uber#declare-test-build-inputs)**: Use it when A test target invokes a broad preparatory build even though it exercises only a subset of services. Check before adopting: Declare every runtime input and keep the compile-only gate. Image tags need a hash of the declared contents (the follow-up below), and test-result reuse depends on hermetic inputs and environment.
- **[Uber](https://starsling.dev/ci-playbooks/uber#build-test-targets)**: Use it when A broad test command also builds non-test targets that another required job already compiles. Check before adopting: Check the build tool's exact target-selection rules, keep every discovered test, and keep compile-only targets in a required job.

#### Stripe: Build only what the analysis consumes

Stripe iOS stopped packaging XCFrameworks for the API check, which compares arm64 Swift interfaces only. Dead-code scanning builds the SDK frameworks directly and hands the fresh index to Periphery, after verifying the expected SDK roots. Stripe Android’s Financial Connections end-to-end job builds only the release variant it tests.

Evidence: Source PR/commit evidence

Use it when: A scan builds artifacts, architectures or test bundles that it never analyzes.

Before applying it: The build the analysis reads stays. Keep all analyzed symbols, required architectures, compiler settings and index completeness, and rebuild when the scheme no longer matches what the scan expects.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#build-only-analysis-inputs)

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

#### Uber: Build only the services each test consumes

SubmitQueue stopped building every Linux service before integration and end-to-end tests. Each test declares the cross-compiled binaries, Dockerfiles and configurations it consumes, and a minimal Docker context is staged from those inputs. OPT13 is adapted: the build stays, scoped to declared target dependencies.

Evidence: Source PR/commit evidence

The author reports all nine Docker suites and 79 unit tests passing locally, plus cached test results on a second run. ([Source](https://github.com/uber/submitqueue/pull/439))

Use it when: A test target invokes a broad preparatory build even though it exercises only a subset of services.

Before applying it: Declare every runtime input and keep the compile-only gate. Image tags need a hash of the declared contents (the follow-up below), and test-result reuse depends on hermetic inputs and environment.

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

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

#### Uber: Build test dependencies in the test lane

SubmitQueue added --build_tests_only to its Bazel unit-test command and kept bazel build //... in a separate required job, so the test lane builds only the test dependency closure.

Evidence: Source PR/commit evidence

The merged Makefile narrows the unit-test build scope, and the separate build job keeps compile-only validation. ([Source](https://github.com/uber/submitqueue/pull/701))

Use it when: A broad test command also builds non-test targets that another required job already compiles.

Before applying it: Check the build tool's exact target-selection rules, keep every discovered test, and keep compile-only targets in a required job.

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

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

### [Install tools only in the jobs that need them](https://starsling.dev/ci-playbooks/techniques/install-consumed-tools)

Keep lightweight discovery and validation jobs from paying for tools used only by other stages.

#### 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#discovery-install)**: Use it when A metadata or graph-discovery job spends more on setup than its actual decision. Check before adopting: Pin the detector version and prove its dependency discovery works without install scripts.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#install-only-consumed-tools)**: Use it when A shared setup step installs browsers, language runtimes or CLIs for every job. Check before adopting: Trace indirect script and action dependencies before moving setup, and declare every tool a job needs explicitly.

#### Mastra: Install only what the change detector needs

Change-detection jobs disabled the workspace install and used a standalone Turbo executable. Later graph discovery installed only its root tools.

Evidence: Source PR/commit evidence

Current graph discovery uses a filtered tools install. ([Source](https://github.com/mastra-ai/mastra/blob/f92e947a448a47515acf14df1eddc5fd5c36f424/.github/workflows/prebuild.yml))

Use it when: A metadata or graph-discovery job spends more on setup than its actual decision.

Before applying it: Pin the detector version and prove its dependency discovery works without install scripts.

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

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

#### Stripe: Install tools only where they are used

Stripe Android installed Ruby in its shared prepare step for every check. It moved the install into the screenshot and dependency-update jobs that actually run Ruby.

Evidence: Source PR/commit evidence

Ruby installation, about 2 minutes per the author, now runs only in the jobs that use Ruby. ([Source](https://github.com/stripe/stripe-android/pull/10184))

Use it when: A shared setup step installs browsers, language runtimes or CLIs for every job.

Before applying it: Trace indirect script and action dependencies before moving setup, and declare every tool a job needs explicitly.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#install-only-consumed-tools)

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

### [Reject irrelevant events before allocating runners](https://starsling.dev/ci-playbooks/techniques/gate-irrelevant-events)

Put event and change conditions before runner allocation so irrelevant comments or edits do not start expensive jobs.

#### 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#comment-gates)**: Use it when Comment-triggered automation starts expensive checks for ordinary discussion. Check before adopting: Event matching and command parsing must agree. Preserve author authorization, edit/removal handling and fork trust, and keep permission checks separate from event filtering.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#isolate-metadata-edits)**: Use it when Description-only edits rerun the same source checks or cancel an active run. Check before adopting: Title validators, retargets and metadata checks still need their events. A skipped run must never overwrite a failed, pending or missing required result.

#### Mastra: Do not start a runner for irrelevant comments

Mastra gated the major-version job on the exact command in the new or previous comment body. This adapts the non-content-event recipe to issue_comment, preserving command removal as well as addition.

Evidence: Source PR/commit evidence

Use it when: Comment-triggered automation starts expensive checks for ordinary discussion.

Before applying it: Event matching and command parsing must agree. Preserve author authorization, edit/removal handling and fork trust, and keep permission checks separate from event filtering.

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

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

#### OpenAI: Keep metadata edits from restarting source validation

Codex Security skips its source checks on body-only PR edits, gives those events their own concurrency group, and renames the skipped aggregates. Title, base and combined edits still run full validation.

Evidence: Source PR/commit evidence

Body-only edits skip source validation in their own concurrency group ([Source](https://github.com/openai/codex-security/pull/1708))

Use it when: Description-only edits rerun the same source checks or cancel an active run.

Before applying it: Title validators, retargets and metadata checks still need their events. A skipped run must never overwrite a failed, pending or missing required result.

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

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

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

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

### [Bake stable dependencies into runner images](https://starsling.dev/ci-playbooks/techniques/prebuild-runner-images)

Prepare versioned system dependencies and build downloads once in the runner image instead of installing them on every job.

#### 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#preinstall-in-the-ci-image)**: Use it when Multiple jobs repeat stable OS-package setup on equivalent environments. Check before adopting: Pin and rebuild the image, patch vulnerabilities and measure image-pull cost. Repository secrets and mutable build outputs do not belong in the image.

#### Linear: Bake stable system dependencies into the CI image

Each API test shard spent 7 to 8 seconds installing the same Postgres client with apt. Linear moved it into a small CI base image with Node and the client, and later added the native build headers whose downloads could occasionally hang.

Evidence: Company-reported result or observation

Together, the CI base image, the filtered install and dropping the node_modules cache reduced per-shard setup time by roughly 44%, from 110-140 seconds to 67-73 seconds. ([Source](https://linear.app/now/ci-bottleneck-reworked#don%27t-cache-when-it%27s-faster-to-rebuild))

Use it when: Multiple jobs repeat stable OS-package setup on equivalent environments.

Before applying it: Pin and rebuild the image, patch vulnerabilities and measure image-pull cost. Repository secrets and mutable build outputs do not belong in the image.

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

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

### [Build before starting the emulator](https://starsling.dev/ci-playbooks/techniques/avoid-emulator-build-contention)

Compile the app first, then boot the emulator and install the finished build.

#### 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#avoid-emulator-build-contention)**: Use it when Compilation runs while an emulator competes for the same CPU and memory. Check before adopting: Keep the device and ABI coverage other lanes require, and measure disk headroom before removing cleanup.

#### Stripe: Build before starting the emulator

Stripe React Native moved app compilation ahead of emulator startup and installed the finished APK. It also built only the emulator’s x86_64 architecture and removed a disk-cleanup step, which the author estimated at 20 seconds.

Evidence: Source PR/commit evidence

Use it when: Compilation runs while an emulator competes for the same CPU and memory.

Before applying it: Keep the device and ABI coverage other lanes require, and measure disk headroom before removing cleanup.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#avoid-emulator-build-contention)

[Primary source](https://github.com/stripe/stripe-react-native/pull/2111)

### [Keep full history and download old blobs on demand](https://starsling.dev/ci-playbooks/techniques/blobless-history)

Change detection often needs commit history and filenames, not old file contents.

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

- **[Mastra](https://starsling.dev/ci-playbooks/mastra#blobless-history)**: Use it when Checkout transfer dominates jobs that compare names or trees across commits. Check before adopting: Lazy blob reads still need network access and appropriate credentials.

#### Mastra: Keep full history and download old blobs on demand

Seven full-history checkouts gained filter: blob:none while retaining fetch-depth: 0. History stays complete, and old blobs download on demand.

Evidence: Source PR/commit evidence

The PR reports transfer falling from 1,572 MB to 189 MB, about 88% fewer bytes. ([Source](https://github.com/mastra-ai/mastra/pull/21298))

Use it when: Checkout transfer dominates jobs that compare names or trees across commits.

Before applying it: Lazy blob reads still need network access and appropriate credentials.

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

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

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

### [Serve checkouts from pre-warmed repository clones](https://starsling.dev/ci-playbooks/techniques/git-checkout-service)

Keep repository clones warm on a few machines and run Git operations against them, so jobs skip the cold clone.

#### 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#git-checkout-service)**: Use it when Many jobs or services clone the same large repository, and each pays for the clone and adds load on the Git server. Check before adopting: Freshness is eventually consistent with the upstream repository, so let a caller ask for the latest state when a job needs it. Adapted for GitHub Actions; compare the original system with yours.

#### Uber: Serve checkouts from pre-warmed repository clones

**Translated to GitHub Actions**

- What Uber built: Uber's GitFarm serves checkouts from pre-warmed bare clones, so clients get a full Git checkout in under 500 milliseconds.
- Your GitHub Actions version: Check out from a pre-warmed repository clone or a shallow fetch, sized to what the job reads.

Cloning Uber's Go monorepo takes roughly 15 minutes and consumes around 6 CPU cores, 32 GB of memory and over 40 GB of disk. GitFarm runs standard Git commands for other services inside ephemeral sandboxes backed by pre-warmed repository checkouts. Each backend keeps one bare clone per repository, updated by push-based events with a fetch every 5 minutes behind them. Clients get a full Git checkout in under 500 milliseconds and no local clone.

Evidence: Company-reported result or observation

Uber reports that clients get a full Git checkout in under 500 milliseconds, and services no longer suffer cold starts of 10-15 minutes due to initial clones on each host. ([Source](https://www.uber.com/us/en/blog/gitfarm-as-a-service/))

Uber reports a CODEOWNERS service's startup time going from 15-20 minutes to under 1 minute, and its CPU from over 70 cores to 16 cores, after switching to GitFarm API calls. ([Source](https://www.uber.com/us/en/blog/gitfarm-as-a-service/))

Use it when: Many jobs or services clone the same large repository, and each pays for the clone and adds load on the Git server.

Before applying it: Freshness is eventually consistent with the upstream repository, so let a caller ask for the latest state when a job needs it.

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

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

[Primary source](https://www.uber.com/us/en/blog/gitfarm-as-a-service/)

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

### [Filter workflows by changed files](https://starsling.dev/best-practices/github-actions/path-filter-workflows)

Use paths and job conditions while keeping required checks reporting.

### [Cancel superseded CI runs](https://starsling.dev/best-practices/github-actions/cancel-superseded-runs)

Cancel obsolete PR runs with a correctly scoped concurrency group.
