- All playbooks
- Compute waste
Compute waste
Find CI work that consumes compute without helping the next result. Compare ways to bound, reuse, or remove that work.
Where to start
Diagnose first: are you paying for redundant jobs, excessive setup, idle wait, or a costly runner class?
Compute cost depends on how much work runs, how long it runs, and the resources it occupies. Start with repeated or obsolete jobs, expensive setup, and workloads that exceed their useful timeout. Compare cost and time together after each change.
12 techniques with source-backed company examples.
Cancel superseded pull request 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.
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.
- 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.
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.
- 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.
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.
- 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.
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.
- 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.
Put time limits on jobs that can hang
Set job timeouts from normal run history so stalled checks release runners and produce a failure that can be investigated.
Bound flaky job runtime
Anthropic describes running tests that were already flaky or failing across the board. As a StarSling pattern for GitHub Actions, a timeout set from successful run history stops a hanging job from holding a runner for six hours.
- Use it when
- A non-matrix integration job has timed-out runs near the 360-minute default and at least three successful runs to measure.
- Before applying it
- A timed-out required check still blocks the merge, so the hanging tests get fixed by name.
Bound integration jobs that can hang
Mastra added explicit timeouts to integration and change-detection jobs, then broadened timeout coverage. Later checkout timeouts were lengthened, showing why the bound needs observed tail data.
The combined store workflow keeps its job timeouts in test-combined-stores.yml. Source
- Use it when
- A database, checkout or external service occasionally hangs beyond useful execution time.
- Before applying it
- A timeout caps the runner minutes a hung job can burn. Leave room for normal tails and diagnostics.
Bound a test job that can hang
After tests looped indefinitely during development, Job Iteration added a five-minute timeout to a build matrix that normally runs in under a minute. A hung leg now stops at five minutes; the platform default is 360.
Merged 2026-06-04; the timeout is on the default branch. Source
- Use it when
- A job with no timeout-minutes can stall and hold a runner until the platform default.
- Before applying it
- Set the ceiling from the job's real duration with headroom for cold setup.
Cap hung test jobs with timeouts
The confidence-sdk-flutter iOS test job could hang on the simulator and run for six hours. The change adds a 45-minute job timeout and a 30-minute timeout on the integration test step, boots the selected simulator before the tests and removes the blocking simctl bootstatus wait.
- Use it when
- A job sometimes hangs and runs until the platform's default limit, six hours on GitHub-hosted runners.
- Before applying it
- Set each timeout well above the longest healthy run so slow runs still finish; the passing iOS run on this change took 6m34s against a 45-minute cap.
Remove repeated build steps
Trace artifact producers and consumers so package, release, or test stages reuse work instead of triggering the same build again.
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.
- 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.
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.
One build per packaging job, run by prepack Source
- 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.
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.
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
- 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.
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.
- 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.
Run CI once per pull request commit
Remove overlapping push and pull request triggers so each change receives one intended validation run.
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.
- 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.
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.
Merged 2026-09-18; the workflow on the default branch is unchanged since. Source
- 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.
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.
- 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.
Bound agent execution and retries
Set explicit time, turn, or retry budgets so an agent that cannot finish returns control rather than consuming unbounded CI capacity.
Give every agent in a CI gate a soft and a hard timeout
One early problem in DoorDash's review agent: the same model request could repeat over and over without advancing its turn counter, so max-turn limits never fired, and a single overall timeout still allowed one stuck agent to burn 20 minutes of tokens. DoorDash moved to tighter per-agent timeouts. The soft timeout interrupts the agent and asks it to drop anything speculative and return only verified findings; the hard timeout is the final kill switch. DoorDash's feature-flag cleanup agents each run under a one-hour hard timeout.
- Use it when
- An agent or long-running step inside CI can loop without progress, and only a job-wide timeout stops it.
- Before applying it
- Set the soft limit from measured run times so normal runs finish untouched, and report a soft-stopped run as partial.
Cap an agent's turns and session retries
Translated to GitHub Actions
- What Spotify built
- Spotify's Fleet Management loop caps a session at 10 turns and three session retries.
- Your GitHub Actions version
- Cap an agent's turns per session and its session retries.
Spotify's first agentic loop for Fleet Management gave the agent a prompt and the files in scope. The agent edited files over a few turns, took in feedback from the build system on each turn, and the task ended once the tests passed or the limits were exceeded: 10 turns per session and three session retries in total. Spotify's prompt guidance for migrations asks for one change at a time, so a session stays within its context window.
- Use it when
- An agent or bot job can iterate on its own, and a job-wide timeout is the only limit on a run.
- Before applying it
- Set the limits from your longest successful sessions. A task that needs more turns is a task to split into smaller changes.
- GitHub Actions adaptation
- Compare the source system with your own workflow before applying this translation.
Run shared validation once
Remove overlapping task or matrix invocations that perform the same check on the same inputs.
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.
- 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.
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.
- 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.
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.
- 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.
Wait until a draft is ready for expensive checks
Avoid spending runner time on expensive validation until a draft pull request becomes ready for review.
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.
Merged 2026-06-16; both lines are on the default branch. Source
- 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.
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.
Drafts skip the full suite, and it runs when the pull request is marked ready for review. Source
- 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.
Package once, and build full installers after merge
Drop the build step that packaging repeats, and build the unpacked app pull requests test.
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.
- 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.
Reduce the setup floor before increasing shard count
Each extra shard repeats checkout, dependencies and container setup.
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.
- Use it when
- More shards would repeat substantial fixed setup work.
- Before applying it
- Remeasure per-shard setup before changing shard count.
Remove caches that cost more than they save
Measure cache restore and upload time against the work avoided. Remove caches whose transfer cost outweighs their benefit.
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.
- 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.
Stop writing caches that no later run restores
Remove cache writes and archives that have no consumer, eliminating work without changing the build result.
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.
- 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).
Company Playbooks
- Read Anthropic’s playbookRun only affected tests
- Read DoorDash’s playbookRun build and test in parallel, sharded across CI agents
- Read Linear’s playbookBenchmark the same job on faster hardware
- Read Mastra’s playbookStarSling runners for the slowest test workflow
- Read OpenAI’s playbookRank CI fixes by the minutes they save
- Read Ramp’s playbookMove a CPU-bound test suite to a faster CPU
- Read Shopify’s playbookLet an agent iterate against one CI metric
- Read Spotify’s playbookBuild arm64 on native arm64 runners
- Read Stripe’s playbookRun only the tests a change can reach
- Read Uber’s playbookBuild the merge-queue speculations most likely to be needed