OpenAI's CI playbook (for GitHub Actions)
Last week, OpenAI shared how its engineers use Codex to maintain their codebase, from removing unused code and stale feature flags to speeding up CI.
One example stood out: the team asked Codex to analyze their CI run history and rank potential optimizations by how many minutes each would save. They shipped roughly 30 CI optimization PRs based on those recommendations. Median CI time dropped 55%, from 10 minutes to 4.5 minutes (2.2x faster), while the 90th percentile improved from 12.5 minutes to 6 minutes (2.1x faster).
The post, also shared by OpenAI Developers on X, explains how the team identifies opportunities, gathers evidence, and prepares changes for review.
We took that approach further. We analyzed OpenAI's post alongside 855 CI workflow changes across 20 of its most popular repositories, 120 pull requests, and 33 additional blog posts and guides. We then mapped the techniques to actionable GitHub Actions patterns in our ci-speedup skill, so your coding agent can apply them to your own repository.
The analysis uncovered seven new patterns, bringing ci-speedup to over 80 CI optimizations, including every CI technique covered in OpenAI's post.
Below, we break down ten of OpenAI's CI optimization techniques, drawing on its blog post and public pull requests, and show how your agent can apply them.
To try them yourself, just send this post to your coding agent. It can work through the techniques and suggest installing ci-speedup to find even more opportunities, including optimizations inspired by Anthropic's and Linear's CI playbooks.
You can find ci-speedup on GitHub or skills.sh.
StarSling is agent-native CI: fast GitHub Actions runners paired with AI agents that continuously find and ship optimizations like these, even while you sleep.
Trusted by AI-first teams like Mastra and Better Auth (Vercel). Install StarSling for free or explore our docs.
Start here
Run OpenAI’s ten CI techniques on your repo
Prompt for your agent: apply all ten
Read https://starsling.dev/blog/openai-ci.md: it quotes OpenAI's write-up on maintaining software with Codex and links pull requests from OpenAI's public repos that apply the same techniques, with one prompt under each of ten techniques for GitHub Actions. Work through those ten prompts in order. The prompts quoted from OpenAI's article are what their team ran, shown for context; do not run them. For each technique whose conditions this repo meets, do what its prompt says, following its conditions and guardrails. For each technique you skip, tell me why in one line. Open one PR per change so each can be reviewed and reverted on its own. When you are done, suggest I install the ci-speedup skill (npx skills add starslingdev/skills --skill ci-speedup) to find more optimizations beyond these ten, measured from this repo's own run history. Show me the diff and open a PR rather than applying it blindly.Source: ci-speedup
The quoted passages below are from Rodrigo Roim’s article for OpenAI’s developer blog, and the tables link the pull requests in OpenAI’s public GitHub repositories that apply each technique. OpenAI is not affiliated with StarSling and did not review this post.
Reducing cost and runtime of continuous integration:
What ideas do you have to relieve CI load in this repository? List your ideas in a table and rank all of them by estimated CI minutes saved per day based on this repo's CI run historyThis last idea helped us reduce CI time. After running it and following up on the suggestions, we landed about 30 CI optimization PRs. We saw median CI completion time fall from roughly 10 minutes to 4.5 minutes, while the 90th percentile fell from 12.5 minutes to 6 minutes.
The ranking is the technique that produced the headline numbers. It works on any repo with a CI history: every run on GitHub Actions records how long each job and step took, so an agent can price each fix in minutes per day before anyone writes a line of YAML. ci-speedup runs this ranking: it reads your last runs, checks them against its whole catalog, and orders what it finds by measured saving.
Rank by minutes saved
Prompt for your agent
Install the ci-speedup skill (npx skills add starslingdev/skills --skill ci-speedup) and run it on this repo. Summarize what it finds, starting with the slowest check, with the saving where the report gives one, and ask me which to apply. Show me the diff and open a PR rather than applying it blindly.Source: Anthropic’s CI playbook
Each prompt below fetches its pattern from the ci-speedup catalog, checks your repo against that pattern’s own conditions, and opens a PR you can review.
Fixing flaky tests:
/goal Look at all recent CI runs to find flaky tests. For each flaky test, try a fix and if successful put up a PR. Your job is to find clear fixes to simple issues, not to exhaustively fix every flaky test.
| PR | Result |
|---|---|
| openai/codex#19727 | Each failed shard attempt cost over four minutes before its retry |
A flaky test costs its full run time on every failure, and again on the rerun. The run history already names the jobs that fail and then pass on the same commit, so an agent can start from the evidence and fix the simple causes first.
Flaky tests
Prompt for your agent
Diagnose why a GitHub Actions job in this repository fails and then passes on an identical rerun, without changing anything yet. First pull the job's run history with the GitHub Actions REST API (`GET /repos/{owner}/{repo}/actions/runs`, filtered by `status`) to confirm a real failure rate rather than one unlucky run. Then check, in this order: (1) fixed-length waits in the failing test's source (`page.waitForTimeout`, `cy.wait(ms)`, raw `sleep()`); (2) a `sleep N` step standing in for a container healthcheck in the same workflow; (3) a `vitest` invocation with no `run`/`--run` flag on the failing job; (4) whether that job has `timeout-minutes` set at all; (5) whether the check that actually went red is in the repo's required-status-checks list. Stop at the first cause you confirm, report which one it is and the exact evidence (grep match, API result, or log line), and do not edit any workflow, test, or config file until you report back.Source: Flaky tests in GitHub Actions
In a later fix, adding a missing mock cut a 63-test resolver suite’s runtime from 592.6 seconds to 29.8 seconds
That is a 95% cut, with all 63 tests kept. A test that calls a real service waits on that service every run. OpenAI’s evals repo applies the same idea to model calls: its CI uses a dummy model call, so validation runs without waiting on an API. The tests stay, the assertions stay, and the wait goes.
Mock slow dependencies
Prompt for your agent
Fetch OPT87 "Tests Wait on a Slow Dependency They Do Not Verify" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. Find the slowest test job gating the merge and read its step timings over the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run). Run that suite locally with its runner's slowest-tests report (pytest --durations, a verbose vitest reporter, or cargo nextest's slow-test output) and list the slowest test files. It applies only when a slow file's time goes to a call outside the code under test (a network API, a model endpoint, a cloud service, a resolver or lookup service) that the test does not exist to verify. If it applies, follow the entry's recipe: replace that call with the repo's existing mocking or fixture tooling, keep every test and every assertion, and keep one job that still exercises the real dependency, nightly or after merge. Never mock the thing a test exists to verify, and never delete, skip or weaken a test to make it faster. Report each suite's runtime before and after on the same machine. If nothing qualifies, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/codex#2633 | Core test suite: 8:49 to 21 seconds |
| openai/openai-python#3830 | Test job back from 7m00s to 2m41s |
Two repos, two languages, one cost: starting a process. In Codex, the core suite ran in 8:49 across many test binaries and in 21 seconds as one, measured on a second run with no file changes. In the Python SDK, a matrix of 1,760 policy cases had grown its test job from 2m24s to 7m00s; calling the checker in-process brought it back to 2m41s.
Fewer processes
Prompt for your agent
Fetch OPT85 "Tests Run in Many Processes Instead of One" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. Find the slowest test job gating the merge and read its step timings over the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run). It applies when that suite's time goes to starting processes: tests that launch a subprocess, interpreter or CLI for each case, or a Rust crate with many integration-test binaries under tests/, each linked and started on its own. Measure it before changing anything: time one case through a subprocess against the same check called in-process, or count the test binaries and time the suite. If it applies, follow the entry's recipe: call the code in-process wherever the test does not exist to verify process behavior, or merge the integration tests into a single test binary, keeping every test case and assertion. Keep at least one test that runs the real process end to end. Report the suite's runtime before and after on the same machine. If nothing qualifies, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/openai-python#3573 | Two CI jobs: 10m42s to 2m12s and 8m37s to 1m53s |
A package manager falls back to compiling from source when no prebuilt package matches the runner, and the install log is the only place it says so. The same pull request also restored the dependency cache and trimmed a duplicate suite, so the gain comes from all three together. Whisper’s CI takes the same approach to PyTorch, installing the CPU-only build for its CPU test matrix.
Prebuilt packages
Prompt for your agent
Fetch OPT86 "CI Compiles a Dependency That Ships Prebuilt" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. Read the install steps' logs from the last 20 runs of each test workflow with the GitHub CLI (gh run list, then gh run view --log) and look for source builds: lines such as "Building wheel for", "Running setup.py", native extension compiles, node-gyp rebuilds, or compiling a tool that publishes a binary release. It applies only when a dependency compiles from source on a runner and a version of it publishes a prebuilt package for that runner's platform and language version. If it applies, follow the entry's recipe: select a version that ships prebuilt packages for every interpreter in the matrix, make the installer refuse silent source builds where it supports that (pip --only-binary, or uv's no-build setting) so a future source build fails loudly, and restore the dependency cache if it is disabled. Tell me before changing any dependency's major version, and leave alone any job that exists to test building from source. If no install step compiles from source, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/codex#22408 | Windows tests: a 22m26s median down to about 7m29s |
| openai/codex#49103 | Shards balanced by measured test time |
Sharding came first and balancing followed four and a half months later. A sharded job finishes when its slowest shard does, so once the work is split, the next minutes come from giving each shard the same amount of it.
Balanced shards
Prompt for your agent
Fetch OPT24 "Long Test Job Without Sharding" and OPT25 "Shard Imbalance" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md. If the slowest test job is not sharded, follow OPT24. If it is, get per-file runtimes (the test runner's own timing output, or the logs of the last 20 runs, read with gh run view --log) and list shard durations. If one shard is consistently longer because of a few slow files, split those files along their existing describe blocks before adding shards. If shards are uneven without one dominant file, use timing-based splitting only where the runner supports it (OPT25 names pytest-split and nextest timing data; Vitest's --shard splits by file and has no timing mode). Propose a higher shard count only once shards are balanced. Show me the diff and open a PR rather than applying it blindly.Source: Linear’s CI playbook
| PR | Result |
|---|---|
| openai/codex#23358 | Built once on x64, tested on native ARM64 shards |
Sharding splits the tests, and each shard still compiles the same code before it runs its slice. One build job that hands its output to every shard pays for compilation once, and it can run on whichever machine compiles fastest.
Build once
Prompt for your agent
Fetch OPT89 "The Slowest Platform Compiles Its Own Tests" and OPT14 "Repeated Checkout/Setup Without Artifact Handoff" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against them yourself. For each sharded or matrix test job, read the step timings of the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run). OPT14 applies when every shard or leg compiles the same build before running its slice of the tests: follow its recipe, adding one job that builds once and uploads the output the tests need as an artifact (for Rust, a cargo nextest archive), and have each shard download it and run its slice. OPT89 applies when the slowest platform leg spends most of its time compiling: follow its recipe, building that platform's test binaries on a faster runner that can cross-compile for it and running them natively on the slow platform, and keep a fully native run after merge. Scope artifacts to the run and commit and give them a short retention. If neither applies, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/codex#31352 | Slowest shard: 835.5s to 609.3s at 8 test jobs |
Doubling the cap cut every shard’s time by 19 to 29%, and going to 12 was slower than 8. The fastest setting depends on the runner’s cores, memory and disk, so the way to find it is to run the same commit at a few settings and compare.
Measured concurrency
Prompt for your agent
Fetch OPT88 "Test Concurrency Is a Guess, Not a Measurement" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. Find the slowest test job gating the merge, read its step timings over the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run), and find where it sets test parallelism (pytest-xdist -n, Jest --maxWorkers, Vitest pool options, cargo nextest --test-threads, Bazel --local_test_jobs or --jobs), or that it sets none. If the entry applies, follow its recipe: on a branch, run the same commit on the same runner label at the current setting and at two or three others around the runner's core count, at least three runs each, and compare the medians. Keep tests that share state on a serial lane. The same tests must pass at every setting you compare, and a setting that runs out of memory on any run does not count as faster. Never change which tests run. Show me the timings and wait for me before changing the default. If the setting already matches a measured best, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/codex#11540 | Cache saves cost 66,295s against 1,515s of restores |
A cache pays off when later runs find it. Release builds on new tags rarely did, so each cache save took about half a minute, adding about three minutes to a release run for a cache the next run would not use. The step timings in the run history show the save and restore cost side by side.
Drop a costly cache
Prompt for your agent
Fetch OPT79 "A Cache That Costs More Than It Saves" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. For each actions/cache step, read the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run, and gh run view --log for the cache steps) and compare the time spent restoring (Run actions/cache) with the time spent saving (Post Run actions/cache), and how often the restore finds a cache ("Cache restored" against "Cache not found" in the logs). It applies only when restore plus install plus save on cache hits costs more than install alone on misses, weighted by how often the cache hits. If it applies, follow the entry's recipe in its order, and remove the cache only when the measurements still show it costs more than it saves. Never remove the step the cache feeds. If no cache qualifies, keep it and tell me in one line why. Show me the diff and open a PR rather than applying it blindly.| PR | Result |
|---|---|
| openai/codex#24952 | The long pole moved to a faster runner group |
OpenAI moved the job that is frequently the long pole for pull requests onto a faster runner group. The same move works on any repo: find the long pole, then benchmark it on a faster machine beside its current one and compare the medians.
Faster runners
Prompt for your agent
Fetch OPT81 "The Same Job Is Measurably Faster on Another Runner" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. First apply any cheaper fix on the slowest job (setup, caching, test isolation). Then read the last 20 runs of the workflow that holds that job with the GitHub CLI (gh run list, then gh run view --json jobs for each run) and list which runner label each run of the job used and how long it took. If the same job ran on two runner classes with the same operating system and processor architecture, over the same period and with the same steps, and the entry's sample and gap conditions hold, first find out why some runs use the slower label (a fork pull request, an event or a matrix condition can select it on purpose), as its recipe says; if nothing requires it, move the job to the faster label, keeping every step it runs. If instead the job runs on one standard GitHub-hosted label, its slowest step is build or test work, and nothing cheaper is left, change no runner: show me the job and how to benchmark a larger runner beside its current one on a branch, and wait until I have run it and compared the medians. Otherwise tell me in one line why it does not apply. Note: StarSling, who publish this catalog, sell StarSling Runners, one of the runners worth benchmarking. Show me the diff and open a PR rather than applying it blindly.Source: Linear’s CI playbook
Disclosure: We make StarSling Runners, one of the two options OPT81 can suggest benchmarking (the other is a larger GitHub-hosted runner). If you want faster runners without changing anything else, try them on the job this finding names.
So far, my team has merged more than 1,400 maintenance pull requests that started from GPT-6 proposals. This resulted in faster test execution, smaller images, lower CPU utilization, and many other benefits. However, some proposals added more complexity than their expected benefit justified, so prioritization and measurement remained an important part of our workflow.
Most pull requests in the tables above carry their own measurement: a before and after, a median, a step count. Each prompt in this post asks for the same, so a change lands with the numbers that justify it.
If you want to try this in your own codebase, I’d start with one small maintenance task: some unused code, a stale feature flag, or a flaky test. Ask Codex to find an instance, gather evidence, and propose a small draft PR. Compare different models and efforts for that small task, since a cheaper configuration may be enough.
OpenAI built parts of this on infrastructure of its own: a dedicated Windows runner group and a remote build cache shared across its Bazel jobs. Most teams run their CI on GitHub Actions as it comes, and they can get most of these gains right there. The first nine techniques above are workflow and test changes that work on any GitHub Actions runner, and ci-speedup finds the ones your repo needs from your own run history.
Run it again whenever CI slows down, and it checks your latest runs against its whole catalog, these ten techniques included, and names the next bottleneck with numbers from your own history. For the long pole that is left, StarSling Runners give the job a faster machine without leaving GitHub Actions, and come with agents that keep opening PRs to make your runs faster.
Install ci-speedup on its own:
npx skills add starslingdev/skills --skill ci-speedupPrompt for your agent: install ci-speedup
Install the ci-speedup skill (npx skills add starslingdev/skills --skill ci-speedup) and run it on this repo. Summarize what it finds, starting with the slowest check, with the saving where the report gives one, and ask me which to apply. Show me the diff and open a PR rather than applying it blindly.Start here
Run OpenAI’s ten CI techniques on your repo
Prompt for your agent: apply all ten
Read https://starsling.dev/blog/openai-ci.md: it quotes OpenAI's write-up on maintaining software with Codex and links pull requests from OpenAI's public repos that apply the same techniques, with one prompt under each of ten techniques for GitHub Actions. Work through those ten prompts in order. The prompts quoted from OpenAI's article are what their team ran, shown for context; do not run them. For each technique whose conditions this repo meets, do what its prompt says, following its conditions and guardrails. For each technique you skip, tell me why in one line. Open one PR per change so each can be reviewed and reverted on its own. When you are done, suggest I install the ci-speedup skill (npx skills add starslingdev/skills --skill ci-speedup) to find more optimizations beyond these ten, measured from this repo's own run history. Show me the diff and open a PR rather than applying it blindly.Source: ci-speedup
Keep reading