---
title: "Caching and setup"
description: "Reuse dependencies and build outputs where it saves time. Compare cache and setup changes, including the cost of restoring and saving them."
url: https://starsling.dev/ci-playbooks/categories/caching
canonicalUrl: https://starsling.dev/ci-playbooks/categories/caching
---

# Caching and setup

Reuse dependencies and build outputs where it saves time. Compare cache and setup changes, including the cost of restoring and saving them.

## Where to start

Diagnose first: does cache restore plus save cost less than the download or build it avoids, across both cache hits and misses? A useful cache saves more work than it adds. Compare restore, install, build, and save timings across equivalent runs. Use the examples below to choose what to cache, when to refresh it, and which setup work to share.

## Apply these techniques

````text
Evaluate this CI improvement area in my repository: Caching and setup.

Reuse dependencies and build outputs where it saves time. Compare cache and setup changes, including the cost of restoring and saving them.

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

19 techniques with source-backed company examples.

### [Cache package downloads and dependency stores](https://starsling.dev/ci-playbooks/techniques/reuse-package-downloads)

Restore package-manager downloads or dependency stores while keeping a locked install that validates the requested dependency graph.

#### 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#cache-go-modules)**: Use it when Go jobs download modules or rebuild unchanged packages on every run. Check before adopting: Key the cache on go.sum and the toolchain version, and measure restore time against a cold run on your runner.
- **[Linear](https://starsling.dev/ci-playbooks/linear#cache-pnpm-store)**: Use it when pnpm installs repeatedly download the same dependency content. Check before adopting: Keep the install and every check running on a cache hit; the cache shortens the download.
- **[Linear](https://starsling.dev/ci-playbooks/linear#cache-yarn-store)**: Use it when Yarn installs repeatedly fetch the same dependency content. Check before adopting: Point the cache at the actual lockfile, keep frozen installs, and keep untrusted runs from writing caches that privileged jobs restore.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#cache-native-dependencies)**: Use it when Package-manager caches hit while native dependency setup stays cold. Check before adopting: Cache dependency stores and keep native build outputs out of the cache.
- **[Spotify](https://starsling.dev/ci-playbooks/spotify#bazel-caches)**: Use it when Bazel jobs download toolchains and external dependencies and rebuild every output on each run. Check before adopting: Restore-only pull request runs keep untrusted code from writing shared caches; a disk cache per Bazel version keeps versions apart.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#use-managed-gradle-cache)**: Use it when Gradle cache maintenance is ad hoc, or builds never reuse cached task outputs. Check before adopting: The source PR changed caching, build flags and emulator ordering together. Measure the cache action on its own, and audit cache paths, trust scope and configuration-cache encryption.
- **[Uber](https://starsling.dev/ci-playbooks/uber#cache-build-inputs)**: Use it when Ephemeral CI jobs repeatedly rebuild the same inputs, or a persistent build process keeps stale state after a checkout changes. Check before adopting: Keep cache validity under the build tool's control and restrict cache writes across trust boundaries. Treat a hung build server as its own problem.

#### DoorDash: Cache Go modules and build output

The first CI workflow for the project pins Go from go.mod and sets the setup action's cache input explicitly (the action caches by default in the version it uses), which restores downloaded modules and build output on later runs. Tests run with -count=1, so Go executes them every time.

Evidence: Source PR/commit evidence

Use it when: Go jobs download modules or rebuild unchanged packages on every run.

Before applying it: Key the cache on go.sum and the toolchain version, and measure restore time against a cold run on your runner.

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

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

#### Linear: Cache the pnpm store, in the right setup order

Linear’s SDK repository installs pnpm before setup-node and caches the pnpm store in its build, release, schema and dependency workflows, keyed on pnpm-lock.yaml.

Evidence: Source PR/commit evidence

Use it when: pnpm installs repeatedly download the same dependency content.

Before applying it: Keep the install and every check running on a cache hit; the cache shortens the download.

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

[Primary source](https://github.com/linear/linear/pull/1130)

#### Linear: Cache Yarn downloads while keeping a locked install

Linear’s Zapier integration enables setup-node’s Yarn cache and installs with yarn install --frozen-lockfile, in its test job and its new release workflow.

Evidence: Source PR/commit evidence

Use it when: Yarn installs repeatedly fetch the same dependency content.

Before applying it: Point the cache at the actual lockfile, keep frozen installs, and keep untrusted runs from writing caches that privileged jobs restore.

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

[Primary source](https://github.com/linear/linear-zapier/pull/101)

#### Shopify: Cache the native dependencies behind a JavaScript build

The React Native jobs already restored pnpm and Bundler caches, while the Android jobs still downloaded Gradle distributions during each run. Checkout Kit turned on the setup-java Gradle cache, keyed on the Android, React Native and sample-app Gradle and package files.

Evidence: Source PR/commit evidence

Merged 2026-05-21; the Gradle cache is on the default branch. ([Source](https://github.com/Shopify/checkout-kit/pull/182))

Use it when: Package-manager caches hit while native dependency setup stays cold.

Before applying it: Cache dependency stores and keep native build outputs out of the cache.

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

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

#### Spotify: Cache Bazel downloads and build outputs

target-determinator's presubmit and release jobs now start with setup-bazel, which caches Bazelisk and downloaded Bazel versions, the external repository cache, and a disk cache for build outputs keyed per tested Bazel version. Pull request runs restore the caches without saving them, so trusted builds on main populate them.

Evidence: Source PR/commit evidence

Use it when: Bazel jobs download toolchains and external dependencies and rebuild every output on each run.

Before applying it: Restore-only pull request runs keep untrusted code from writing shared caches; a disk cache per Bazel version keeps versions apart.

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

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

#### Stripe: Use the ecosystem’s cache integration

Stripe React Native replaced hand-maintained Gradle cache paths with gradle/actions/setup-gradle and built the Android app with --build-cache.

Evidence: Source PR/commit evidence

With full caches, the author measured new-architecture app builds at 2:34 against 8:03 and old-architecture builds at 1:42 against 3:50, across all the PR’s changes. Stripe React Native later moved these tests to another CI system in stripe/stripe-react-native#2137. ([Source](https://github.com/stripe/stripe-react-native/pull/2111))

Use it when: Gradle cache maintenance is ad hoc, or builds never reuse cached task outputs.

Before applying it: The source PR changed caching, build flags and emulator ordering together. Measure the cache action on its own, and audit cache paths, trust scope and configuration-cache encryption.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#use-managed-gradle-cache)

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

#### Uber: Persist the build cache between runners

Tango added a persistent Bazel disk cache to its integration job. The same PR runs nested queries in a fresh batch-mode Bazel process and raises the outer request timeout. OPT61 is extended from dependency installs to content-addressed build outputs.

Evidence: Source PR/commit evidence

The PR reports a local pass in 81s and a successful CI integration job in 6m45s. ([Source](https://github.com/uber/tango/pull/219))

Use it when: Ephemeral CI jobs repeatedly rebuild the same inputs, or a persistent build process keeps stale state after a checkout changes.

Before applying it: Keep cache validity under the build tool's control and restrict cache writes across trust boundaries. Treat a hung build server as its own problem.

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

[Primary source](https://github.com/uber/tango/pull/219)

### [Reuse downloaded tools across runs](https://starsling.dev/ci-playbooks/techniques/cache-tool-downloads)

Cache versioned tools and browser downloads so jobs can restore them while retaining installation and repair checks.

#### 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#cache-lint-tools)**: Use it when Installing or compiling a lint tool takes a large share of the lint job. Check before adopting: The cache key covers the runner OS, the tool version and every pinned revision the tool is built against.
- **[Linear](https://starsling.dev/ci-playbooks/linear#cache-tool-downloads)**: Use it when A pinned command-line tool or matrix leg downloads the same packages on every run. Check before adopting: OPT2’s recipe targets large binaries; here its version-pinned cache key applies to npm package downloads. Key the cache on every version that changes the download, and keep the step that installs or runs the tool.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#cache-browser-downloads)**: Use it when Browser downloads take a large share of a job's setup on every run. Check before adopting: A browser cache can take as long to restore as a fresh download; compare cold, warm and changed-lockfile runs before keeping it.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#reuse-downloaded-toolchains)**: Use it when Every CI job repeats a large toolchain download or installer bootstrap. Check before adopting: Key the reuse on exact version, OS and architecture, verify a published checksum, and install atomically so a partial download is never reused.

#### Anthropic: Cache compiled lint tools between runs

Bun cached the cargo-dylint binaries on the tool version, and the dylint driver and mordant library on the version plus the pinned mordant revision. A cache hit skips only the install; the workspace check still runs on every change. The cache carried over when Bun folded its Rust lint jobs into one workflow.

Evidence: Estimate

A cold run takes about 6.5 minutes; the PR expects warm runs to take about a minute and a half (estimate). ([Source](https://github.com/oven-sh/bun/pull/38459))

Use it when: Installing or compiling a lint tool takes a large share of the lint job.

Before applying it: The cache key covers the runner OS, the tool version and every pinned revision the tool is built against.

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

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

#### Linear: Cache pinned tool and matrix downloads

The same SDK change caches the npm downloads of a version-pinned npx tool and of each smoke-test matrix leg, with keys that name the tool version or the matrix’s Node and TypeScript versions.

Evidence: Source PR/commit evidence

Use it when: A pinned command-line tool or matrix leg downloads the same packages on every run.

Before applying it: OPT2’s recipe targets large binaries; here its version-pinned cache key applies to npm package downloads. Key the cache on every version that changes the download, and keep the step that installs or runs the tool.

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

[Primary source](https://github.com/linear/linear/pull/1130)

#### Shopify: Reuse browser downloads and keep the repair step

Checkout Kit caches Playwright Chromium in its default Linux cache directory, keyed by the runner's operating system and the web lockfile. The install command still runs after the restore, so it repairs a stale or partial cache and installs system dependencies.

Evidence: Source PR/commit evidence

Merged 2026-10-12; the cache step is on the default branch. ([Source](https://github.com/Shopify/checkout-kit/pull/972))

Use it when: Browser downloads take a large share of a job's setup on every run.

Before applying it: A browser cache can take as long to restore as a fresh download; compare cold, warm and changed-lockfile runs before keeping it.

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

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

#### Stripe: Reuse a downloaded toolchain

Stripe Android replaced its Java installer with direct Temurin downloads for the configured version, OS and architecture, and reuses an existing install directory on later builds.

Evidence: Source PR/commit evidence

Use it when: Every CI job repeats a large toolchain download or installer bootstrap.

Before applying it: Key the reuse on exact version, OS and architecture, verify a published checksum, and install atomically so a partial download is never reused.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#reuse-downloaded-toolchains)

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

### [Persist a tool’s incremental analysis state](https://starsling.dev/ci-playbooks/techniques/persist-tool-analysis)

Keep reusable configuration or analysis state in addition to dependency downloads, using the tool’s own cache contract.

#### How to choose an approach

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

- **[OpenAI](https://starsling.dev/ci-playbooks/openai#persist-tool-analysis)**: Use it when Lint rebuilds its tools or analysis on every run despite restored dependency caches. Check before adopting: Restore, save and storage can outweigh reuse. Separate writers must never publish incomplete state under another job's key.
- **[Shopify](https://starsling.dev/ci-playbooks/shopify#enable-tool-cache)**: Use it when The build tool supports task caching and the CI commands bypass it. Check before adopting: A cached task whose inputs leave out a file restores stale work; declare shared configuration, lockfiles and tool versions as inputs.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#cache-gradle-configuration)**: Use it when Gradle spends noticeable time configuring the same task graph on every run. Check before adopting: Configuration caches can contain secrets, so use Gradle’s encryption support and keep cache writes inside a trusted boundary. Resolve configuration-cache warnings before relying on it, and keep incompatible tasks such as publishing on the uncached path.

#### OpenAI: Persist the tool’s analysis state separately

In the Go SDK a test job could win the shared cache key before lint saved its tools and analysis. Lint now turns off setup-go caching and owns a cache for modules, Go builds and the golangci-lint cache, keyed per revision with restore fallbacks.

Evidence: Source PR/commit evidence

Dedicated cache for modules, compiled tools and analysis ([Source](https://github.com/openai/openai-go/pull/996))

Use it when: Lint rebuilds its tools or analysis on every run despite restored dependency caches.

Before applying it: Restore, save and storage can outweigh reuse. Separate writers must never publish incomplete state under another job's key.

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

[Primary source](https://github.com/openai/openai-go/pull/996)

#### Shopify: Let the build tool reuse unchanged work

Shopify CLI's scripts and CI jobs passed --skip-nx-cache, and a cache input included the commit SHA. Shopify/cli#8239 declares explicit cached targets with shared inputs and the real package-local dist outputs, then removes the bypass from scripts and CI.

Evidence: Source PR/commit evidence

The PR reports repeated builds and bundles restoring 12 of 12 tasks from cache. ([Source](https://github.com/Shopify/cli/pull/8239))

Use it when: The build tool supports task caching and the CI commands bypass it.

Before applying it: A cached task whose inputs leave out a file restores stale work; declare shared configuration, lockfiles and tool versions as inputs.

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

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

#### Stripe: Cache Gradle configuration as well as dependencies

Stripe Android enabled Gradle’s configuration cache with dedicated restore and save steps. A later change also saved Gradle transforms, and publishing commands run with --no-configuration-cache.

Evidence: Source PR/commit evidence

The author reports slightly faster builds. ([Source](https://github.com/stripe/stripe-android/pull/11749))

Use it when: Gradle spends noticeable time configuring the same task graph on every run.

Before applying it: Configuration caches can contain secrets, so use Gradle’s encryption support and keep cache writes inside a trusted boundary. Resolve configuration-cache warnings before relying on it, and keep incompatible tasks such as publishing on the uncached path.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#cache-gradle-configuration)

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

### [Install prebuilt packages and toolchains](https://starsling.dev/ci-playbooks/techniques/prebuilt-toolchains)

Download compatible prebuilt dependencies instead of compiling the same toolchain or package during every CI 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.

- **[OpenAI](https://starsling.dev/ci-playbooks/openai#prebuilt-packages)**: Use it when Install logs show a source build for a dependency that also ships compatible binaries. Check before adopting: Check interpreter, platform and dependency compatibility for every version in the matrix before pinning.
- **[Uber](https://starsling.dev/ci-playbooks/uber#prebuilt-toolchain)**: Use it when Cold runs spend substantial time compiling an upstream tool that has a compatible, trusted binary release. Check before adopting: Verify platform support, compiler compatibility, checksums and generated output, and keep a source build for platforms without a binary release.

#### OpenAI: Install prebuilt packages

The Python SDK selected NumPy versions that ship wheels for every supported interpreter, so installs download a binary. The same PR restored environment caching and ran only the HTTPX 2 suite in the HTTPX 2 job; the job timings cover all three changes. OpenAI later moved the repository to uv (openai/openai-python#3653).

Evidence: Source PR/commit evidence

HTTPX 2 job 10m42s to 2m12s; Python 3.14 job 8m37s to 1m53s ([Source](https://github.com/openai/openai-python/pull/3573))

Repository moved to uv; the NumPy wheel selection carried into uv.lock ([Source](https://github.com/openai/openai-python/pull/3653))

Use it when: Install logs show a source build for a dependency that also ships compatible binaries.

Before applying it: Check interpreter, platform and dependency compatibility for every version in the matrix before pinning.

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

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

#### Uber: Download the compiler your build depends on

SubmitQueue registered a prebuilt protoc toolchain, so cold builds no longer compile protoc and its C++ dependencies. The same 35 tests passed before and after.

Evidence: Source PR/commit evidence

On the author's machine, a cold make test after bazel clean took about 84s before and about 11s after, with the same 35 tests passing. ([Source](https://github.com/uber/submitqueue/pull/165))

Use it when: Cold runs spend substantial time compiling an upstream tool that has a compatible, trusted binary release.

Before applying it: Verify platform support, compiler compatibility, checksums and generated output, and keep a source build for platforms without a binary release.

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

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

### [Keep unrelated environment changes out of cache keys](https://starsling.dev/ci-playbooks/techniques/content-based-cache-keys)

Include inputs that can change the output while excluding branch identity and unrelated environment churn.

#### 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#task-cache-env)**: Use it when Task hashes change with secrets that are consumed only when the application runs. Check before adopting: Keep credentials used by build-time code generation or embedded configuration. Review secret availability separately from cache hashing.
- **[Mastra](https://starsling.dev/ci-playbooks/mastra#global-cache-env)**: Use it when Identical source and tool inputs miss cache when jobs see different GitHub event environments. Check before adopting: Only remove variables proven irrelevant to output. Cache correctness still requires all output-affecting configuration and secure write permissions.
- **[OpenAI](https://starsling.dev/ci-playbooks/openai#stabilize-cache-environment)**: Use it when Identical source trees miss remote build caches after a runner image update. Check before adopting: A PATH allow-list can hide a required tool. Keep real tool versions in the cache inputs so a toolchain change still invalidates results.

#### Mastra: Keep runtime API keys out of unrelated build hashes

The build task stopped hashing RAPID_API_KEY and ANTHROPIC_API_KEY, two task-level env entries. The transferable step is proving that the build reads no runtime-only credentials.

Evidence: Source PR/commit evidence

Use it when: Task hashes change with secrets that are consumed only when the application runs.

Before applying it: Keep credentials used by build-time code generation or embedded configuration. Review secret availability separately from cache hashing.

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

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

#### Mastra: Remove branch identity from content cache keys

A branch-name global environment variable caused different keys between Prebuild and workflow_run consumers. Mastra removed this artificial cache scope.

Evidence: Source PR/commit evidence

Use it when: Identical source and tool inputs miss cache when jobs see different GitHub event environments.

Before applying it: Only remove variables proven irrelevant to output. Cache correctness still requires all output-affecting configuration and secure write permissions.

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

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

#### OpenAI: Remove unrelated environment churn from cache inputs

Codex traced different remote cache keys for the same source tree to an unrelated tool version in the hosted Windows PATH. It now computes an explicit PATH in PowerShell and passes it to all three Bazel environments, keeping the tools the build uses.

Evidence: Source PR/commit evidence

Bazel actions see one explicit PATH across all three environments ([Source](https://github.com/openai/codex/pull/19161))

Use it when: Identical source trees miss remote build caches after a runner image update.

Before applying it: A PATH allow-list can hide a required tool. Keep real tool versions in the cache inputs so a toolchain change still invalidates results.

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

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

### [Persist compiler output between runs](https://starsling.dev/ci-playbooks/techniques/persist-compiler-cache)

Restore compatible compiler outputs and intermediate objects instead of recompiling unchanged 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.

- **[OpenAI](https://starsling.dev/ci-playbooks/openai#cache-cross-compile-objects)**: Use it when One producer job compiles the same Rust or C++ inputs on every run before handing test binaries to native runners. Check before adopting: Compiler caches need the toolchain, target and flags in their identity, and trusted writers only. Cached compilation leaves native test execution in place.
- **[Ramp](https://starsling.dev/ci-playbooks/ramp#cache-the-compiler)**: Use it when A build job compiles the same native outputs from scratch on every run, and release builds run on tags. Check before adopting: A new tag build starts from the default branch's caches, so the warming run on main must use the same cache key prefix as the tag build.

#### OpenAI: Cache the cross-compile producer’s objects

Codex's archive refactor moved the Windows ARM64 build onto a Windows x64 producer and turned on sccache for it, keying the producer's caches by the archive runner so the cross-compile reuses the Windows x64 cache.

Evidence: Source PR/commit evidence

sccache on for the Windows ARM64 archive producer ([Source](https://github.com/openai/codex/pull/23358))

Use it when: One producer job compiles the same Rust or C++ inputs on every run before handing test binaries to native runners.

Before applying it: Compiler caches need the toolchain, target and flags in their identity, and trusted writers only. Cached compilation leaves native test execution in place.

[Read more in the OpenAI playbook](https://starsling.dev/ci-playbooks/openai#cache-cross-compile-objects)

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

#### Ramp: Persist the compiler cache and warm it from the default branch

Ramp's CLI release workflow compiles the Python CLI to native binaries with Nuitka on every version tag. Ramp rooted Nuitka's caches (ccache on Linux and macOS, clcache on Windows) in the workspace through NUITKA_CACHE_DIR and saved them with actions/cache, keyed on the lockfile with restore prefixes. Caches are scoped per ref, so a new release tag starts from the default branch's caches and shares nothing with other tags; the workflow therefore also runs on every push to main to warm the cache; those runs build but skip packaging, artifact upload and the release jobs. A rerun of the same tag can also reuse that tag's own cache.

Evidence: Source PR/commit evidence

In the commit Release v0.2.26, the release workflow restores the Nuitka compiler cache on every build and warms it on each push to main. ([Source](https://github.com/ramp-public/ramp-cli/commit/4fec456d52fd03f85e1b96c94e075d22b5306c51))

The commit's workflow comments describe the change; the quote is from the diff. GitHub's cache rules let a run restore caches from its own ref and the default branch, so the quoted comment is narrower than that rule.

Use it when: A build job compiles the same native outputs from scratch on every run, and release builds run on tags.

Before applying it: A new tag build starts from the default branch's caches, so the warming run on main must use the same cache key prefix as the tag build.

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

[Primary source](https://github.com/ramp-public/ramp-cli/commit/4fec456d52fd03f85e1b96c94e075d22b5306c51)

### [Reuse Docker build layers across runners](https://starsling.dev/ci-playbooks/techniques/reuse-docker-layers)

Restore image layers from a persistent cache so new runners can avoid rebuilding unchanged 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.

- **[Spotify](https://starsling.dev/ci-playbooks/spotify#registry-layer-cache)**: Use it when One workflow builds images with a layer cache, and another builds from the same Dockerfile without one. Check before adopting: Release builds read the cache that default-branch CI writes, so keep write access to trusted builds.
- **[Stripe](https://starsling.dev/ci-playbooks/stripe#cache-docker-layers)**: Use it when Container dependency layers rebuild on every ephemeral runner. Check before adopting: Keep registry credentials and cache writes inside the trusted boundary, and verify cold and warm builds produce equivalent images.

#### Spotify: Restore image layers from the registry cache

Confidence's CI build on pushes to main writes its Docker layer cache to the container registry. The change builds the release deployer image from a target in the shared Dockerfile and adds cache-from for that registry cache, so the release build restores the layers CI already built.

Evidence: Source PR/commit evidence

Use it when: One workflow builds images with a layer cache, and another builds from the same Dockerfile without one.

Before applying it: Release builds read the cache that default-branch CI writes, so keep write access to trusted builds.

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

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

#### Stripe: Persist Docker build layers across runners

Stripe sync-engine added BuildKit registry caches for its image builds, separated by architecture, so the dependency install layer is reused and only the source copy layer rebuilds.

Evidence: Source PR/commit evidence

Use it when: Container dependency layers rebuild on every ephemeral runner.

Before applying it: Keep registry credentials and cache writes inside the trusted boundary, and verify cold and warm builds produce equivalent images.

[Read more in the Stripe playbook](https://starsling.dev/ci-playbooks/stripe#cache-docker-layers)

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

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

- **[Anthropic](https://starsling.dev/ci-playbooks/anthropic#prefetch-build-inputs)**: Use it when Builds on fresh runners download the same large, versioned inputs on every run, and you control the runner image. Check before adopting: Entries are keyed by exact URL or version, so a bumped dependency downloads fresh until the image is refreshed.

#### Anthropic: Bake versioned build downloads into the runner image

Bun bakes a read-only prefetch store of its dependency tarballs, WebKit prebuilt and zig compiler into its build images. A build copies a prefetched file when its URL matches and downloads from the network when a version has changed.

Evidence: Source PR/commit evidence

Before the change every CI build downloaded about 18 archive tarballs, a 200 MB WebKit prebuilt and a zig compiler before compiling. ([Source](https://github.com/oven-sh/bun/pull/29568))

Use it when: Builds on fresh runners download the same large, versioned inputs on every run, and you control the runner image.

Before applying it: Entries are keyed by exact URL or version, so a bumped dependency downloads fresh until the image is refreshed.

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

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

### [Cache the directories the build actually uses](https://starsling.dev/ci-playbooks/techniques/cache-real-output-paths)

Align restored and saved cache paths with the tool’s real output directories so the next run can use the cached work.

#### 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#repair-cache-contract)**: Use it when Cache steps report success but warm runs still download dependencies or rebuild unchanged targets. Check before adopting: Confirm the paths hold reusable inputs and outputs. The run-specific save key suits a content-addressed cache with stable restore prefixes. Measure restore and upload time, eviction and cold misses, and restrict cache writes across trust boundaries.

#### Uber: Cache the paths the build actually writes

SubmitQueue originally cached ~/.cache/bazel while its build wrote separate disk and repository caches. The correction saves those actual paths, gives each job its own cache entries, and adds a per-run suffix with stable restore prefixes so the content-addressed cache can grow. OPT61 is adapted to repairing an existing cache.

Evidence: Source PR/commit evidence

The author links a first run of about 10m and a second run of 1m24s; the same PR also changes application and test code. ([Source](https://github.com/uber/submitqueue/pull/59))

Use it when: Cache steps report success but warm runs still download dependencies or rebuild unchanged targets.

Before applying it: Confirm the paths hold reusable inputs and outputs. The run-specific save key suits a content-addressed cache with stable restore prefixes. Measure restore and upload time, eviction and cold misses, and restrict cache writes across trust boundaries.

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

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

### [Declare the inputs and outputs of cached builds](https://starsling.dev/ci-playbooks/techniques/declare-cache-inputs)

Make build-cache contracts cover the actual files and configuration consumed and produced by the task.

#### 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#build-inputs)**: Use it when A remote build cache misses after edits that leave the emitted artifact unchanged. Check before adopting: Exclusions must match actual tools, generators and imported assets.

#### Mastra: Make build cache keys describe the build

Mastra declared source, package and compiler configuration inputs for library builds, excluding test files and Markdown where they were not build inputs. Composite tasks received their own input declarations.

Evidence: Source PR/commit evidence

Use it when: A remote build cache misses after edits that leave the emitted artifact unchanged.

Before applying it: Exclusions must match actual tools, generators and imported assets.

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

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

### [Key nested installs from their own lockfile](https://starsling.dev/ci-playbooks/techniques/nested-lockfile)

A checkout in a subdirectory needs a cache key for that dependency graph.

#### 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#nested-lockfile)**: Use it when A shared setup action serves multiple working directories or independent dependency graphs. Check before adopting: Installation still runs after a cache hit. Preserve frozen lockfiles, platform and package-manager compatibility, and trusted/untrusted cache separation.

#### Mastra: Key nested installs from their own lockfile

The shared setup action added a cache-dependency-path input, and trusted nested checkouts passed their own pnpm-lock.yaml. This adapts cache-key granularity to checkout layout.

Evidence: Source PR/commit evidence

Use it when: A shared setup action serves multiple working directories or independent dependency graphs.

Before applying it: Installation still runs after a cache hit. Preserve frozen lockfiles, platform and package-manager compatibility, and trusted/untrusted cache separation.

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

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

### [Prepare shared test inputs once](https://starsling.dev/ci-playbooks/techniques/reuse-shared-test-inputs)

Produce or download common test inputs once and pass them to the jobs that consume them.

#### 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#publish-once)**: Use it when Independent fixture tests repeat the same package publishing and registry preparation. Check before adopting: The producer becomes a shared dependency. Compare its latency plus artifact transfer against repeated work; bind artifacts to the same commit and platform, preserve trust and never fall back silently to public packages.

#### Mastra: Publish test packages once and hand off the registry

Mastra introduced a producer job that publishes required local packages to Verdaccio storage, uploads it, and lets each consumer job download and serve that snapshot.

Evidence: Source PR/commit evidence

Use it when: Independent fixture tests repeat the same package publishing and registry preparation.

Before applying it: The producer becomes a shared dependency. Compare its latency plus artifact transfer against repeated work; bind artifacts to the same commit and platform, preserve trust and never fall back silently to public packages.

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

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

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

### [Restore compatible Python environments](https://starsling.dev/ci-playbooks/techniques/cache-python-environments)

Restore the resolved virtual environment and keep the install step after it.

#### How to choose an approach

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

- **[OpenAI](https://starsling.dev/ci-playbooks/openai#cache-python-environments)**: Use it when Dependency installation repeats on fresh CI runners despite unchanged interpreter and lockfiles. Check before adopting: Virtual environments can hold interpreter-specific paths and binaries. Key on everything that changes them, and keep the install step so a stale cache is repaired.

#### OpenAI: Restore compatible Python environments

The Python SDK added a virtual-environment cache to three jobs, keyed by operating system, architecture, resolved Python version and both lockfiles, with dependency sync still running after the restore. The repository now uses uv's built-in cache (openai/openai-python#3653).

Evidence: Source PR/commit evidence

Environment cache keyed by OS, architecture, Python version and lockfiles ([Source](https://github.com/openai/openai-python/pull/3573))

Repository moved to uv, with uv's built-in cache in CI ([Source](https://github.com/openai/openai-python/pull/3653))

Use it when: Dependency installation repeats on fresh CI runners despite unchanged interpreter and lockfiles.

Before applying it: Virtual environments can hold interpreter-specific paths and binaries. Key on everything that changes them, and keep the install step so a stale cache is repaired.

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

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

### [Restore dependencies after their manifest exists](https://starsling.dev/ci-playbooks/techniques/checkout-before-cache)

Check out the code before the setup action, so its dependency cache can find the manifest.

#### 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#checkout-before-cache)**: Use it when A language setup action runs before checkout, or looks for the dependency manifest in the wrong directory under a custom checkout path. Check before adopting: Use a reviewed action version and a manifest glob that matches only the intended modules, and keep dependency installation and integrity checks on cache hits.

#### Uber: Restore dependencies after their manifest exists

TChannel's Go setup ran before checkout, and checkout used a nested GOPATH directory. The fix runs checkout first, points setup-go at **/go.sum and removes an obsolete Glide cache. OPT61 is adapted to repairing cache discovery.

Evidence: Source PR/commit evidence

The merged diff moves checkout ahead of setup-go and sets cache-dependency-path to the real go.sum location. ([Source](https://github.com/uber/tchannel-go/pull/935))

Use it when: A language setup action runs before checkout, or looks for the dependency manifest in the wrong directory under a custom checkout path.

Before applying it: Use a reviewed action version and a manifest glob that matches only the intended modules, and keep dependency installation and integrity checks on cache hits.

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

[Primary source](https://github.com/uber/tchannel-go/pull/935)

### [Reuse a snapshot of the build store](https://starsling.dev/ci-playbooks/techniques/cache-nix-store)

Restore the newest store snapshot for the dependency inputs, and save a new one after the component tests.

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

- **[Shopify](https://starsling.dev/ci-playbooks/shopify#cache-nix-store)**: Use it when Fresh runners rebuild the same dependency closure on every run. Check before adopting: The three snapshots used about 4.3 GB of the 10 GB repository cache quota; restore took 0.5 to 2 minutes and save 0.3 to 1.6.

#### Shopify: Reuse a snapshot of the build store

Most of a Tecnix run rebuilt third-party dependencies that change only with flake.lock and packaging/dependencies.nix. Shopify/tecnix#74 caches the Nix store as one snapshot per build variant, keyed by a hash of those files plus the commit, restores the newest snapshot with that prefix, and saves only from this repository's main pushes, pull requests and manually dispatched runs.

Evidence: Single-run demonstration

Median PR run over 2026-08-10 to 2026-09-29: 61.8 minutes. After the PR, which makes seven changes (among them the cache, pull requests running only the component tests and the cheap flake checks, and separate sanitizer and coverage jobs), with a 20 to 30% hardware spread measured between runs, a later push with a cache hit and no source change ran 4.7 minutes. One sample per row, as the PR reports. ([Source](https://github.com/Shopify/tecnix/pull/74))

Use it when: Fresh runners rebuild the same dependency closure on every run.

Before applying it: The three snapshots used about 4.3 GB of the 10 GB repository cache quota; restore took 0.5 to 2 minutes and save 0.3 to 1.6.

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

[Primary source](https://github.com/Shopify/tecnix/pull/74)

### [Stop downloading the test image on every build](https://starsling.dev/ci-playbooks/techniques/keep-image-on-runner)

Cache the container image the tests run in, so each build reuses the copy it already has.

#### How to choose an approach

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

- **[Ramp](https://starsling.dev/ci-playbooks/ramp#keep-image-on-runner)**: Use it when Jobs pull or rebuild the same container image on every run and spend a minute or more on it. Check before adopting: A cached image must still rebuild when its inputs change, so key the cache on the Dockerfile and the lockfiles it copies in.

#### Ramp: Stop downloading the test image on every build

Each build pulled the last-built Docker image from the registry, which took 1 to 2 minutes. Ramp's new test runners were self-hosted, so the image stayed in the runner's local Docker cache between builds. On GitHub-hosted runners, which start empty, a job that builds the image may cut the same overhead with a persisted build cache (the OPT4 recipe), and a job that only pulls a prebuilt public image may cut it by saving and loading the image through the Actions cache; measure the restore time against the pull it replaces.

Evidence: Company-reported result or observation

Local Docker caching shaved over a minute off a run that took about fifteen minutes by then. ([Source](https://builders.ramp.com/post/faster-python-tests-part-two))

Use it when: Jobs pull or rebuild the same container image on every run and spend a minute or more on it.

Before applying it: A cached image must still rebuild when its inputs change, so key the cache on the Dockerfile and the lockfiles it copies in.

[Read more in the Ramp playbook](https://starsling.dev/ci-playbooks/ramp#keep-image-on-runner)

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

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

### [Cache workflow dependencies](https://starsling.dev/best-practices/github-actions/cache-dependencies)

Choose dependency cache paths and keys for your package manager.

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

Share expensive setup at the scope your tests need.

### [Share setup steps across jobs](https://starsling.dev/github-actions/optimizations/avoid-duplicated-setup)

Centralize repeated setup definitions and remove duplicate or unused steps.

### [Optimize npm installs](https://starsling.dev/github-actions/optimizations/optimize-npm-install)

Cache npm dependencies and large downloads and remove unused tool installs.

### [Optimize pnpm installs](https://starsling.dev/github-actions/optimizations/optimize-pnpm-install)

Set up the pnpm store cache in the right order and scope recursive commands.
