Docs
Set up Warmbuild
Warmbuild is a remote compilation cache for Xcode 26 and later. A GitHub Actions job and a developer’s Mac share what earlier builds compiled; after each CI job you get a report of where its time went; and pull requests can run only the tests their change reaches. Each part below is independent: start with CI.
Before you start
- Sign in with GitHub and install the Warmbuild GitHub App on the user or organisation that owns the repositories. It asks for metadata, organisation members and your email address; never for code.
- Accounts are approved by hand during the beta. Until then the cache answers “not approved” and builds run as before. Nothing fails.
- Xcode 26 or later on the runner or Mac. GitHub-hosted
macos-26runners have it.
CI: the GitHub Action
Add one step before xcodebuild. The Action starts a cache server and exports XCODE_XCCONFIG_FILE, so xcodebuild stays unchanged. Pushes to the default branch write; everything else reads.
id-token: writelets the Action sign in with the job's GitHub OIDC token; there is no secret to paste.- Optional:
select-tests: trueselects the tests a pull request can reach; pushes and other events run everything. Pass$WARMBUILD_ONLY_TESTINGtoxcodebuild test. - After the job, the summary shows hits, misses and where the job's time went; the dashboard keeps the history per repository.
- Reading the test results takes at most
report-budgetseconds (90); runxcrun simctl shutdown allafter the tests so a busy Simulator does not slow it down. - Optional: the
mikitahimpel/warmbuild-action/test-cachestep betweenbuild-for-testingandtest-without-buildingskips test targets whose built products passed before (see the Action's README).
permissions: id-token: write contents: readjobs: build: runs-on: macos-26 steps: - uses: actions/checkout@v5 - uses: mikitahimpel/warmbuild-action@v1.12.0 with: select-tests: true # optional - run: xcodebuild build -scheme App -destination 'generic/platform=iOS Simulator'Mac: the CLI
Local Xcode builds read what CI compiled on the default branch. Laptops only read.
- Needs macOS and Node 20 or newer. The installer checks the download against its published SHA-256 and installs to
~/.warmbuild/binwithout sudo, offers to add it to PATH, signs you in with a GitHub code, and starts the cache for this repository (also after every reboot). Run it again to update. warmbuild startoffers to add#include? "warmbuild.local.xcconfig"to the project's xcconfig, so Xcode and xcodebuild need no environment variable (Macs without Warmbuild build as before). Or exportXCODE_XCCONFIG_FILE="$HOME/.warmbuild/warmbuild.xcconfig".warmbuild doctorexplains a low hit rate, with the fix. The cache updates itself daily (warmbuild updatenow).warmbuild stopturns it off.- More detail on the Mac setup.
cd your-appcurl -fsSL https://warmbuild-dev.mikitahimpel.com/install.sh | shCoding agents: the skill
Tells coding agents what the warmbuild commands do: status, start/stop, hits and misses, test selection, and the CI report commands (builds, report, findings, why-miss, tests --slow/--flaky).
- For one repository, use
.claude/skills/warmbuild/instead. - For other agents (Codex, Cursor), paste the AGENTS.md snippet into AGENTS.md.
- No shell (Claude on the web, cloud agents)? Add the MCP server
https://warmbuild-mcp-dev.mikitahimpel.com/mcp(how).
mkdir -p ~/.claude/skills/warmbuild && curl -fsSL \ https://raw.githubusercontent.com/mikitahimpel/warmbuild-action/v1.12.0/skills/warmbuild/SKILL.md \ -o ~/.claude/skills/warmbuild/SKILL.mdMCP server: agents without a shell
Agents that cannot run the CLI (Claude on the web and desktop, cloud agents) read the same CI reports through Warmbuild’s remote MCP server. Add its URL as a remote server; the first call opens a sign-in with the same GitHub account as the dashboard, and the agent then reads what you can: your accounts, and repositories shared with you through team access. Every tool only reads.
https://warmbuild-mcp-dev.mikitahimpel.com/mcp
- Claude (web and desktop): Settings → Connectors → Add custom connector, paste the URL.
- Claude Code:
claude mcp add --transport http warmbuild https://warmbuild-mcp-dev.mikitahimpel.com/mcp, then/mcpto sign in. With a shell, the CLI and skill above do more. - Cursor: in
~/.cursor/mcp.json,{"mcpServers": {"warmbuild": {"url": "https://warmbuild-mcp-dev.mikitahimpel.com/mcp"}}}. - Codex: in
~/.codex/config.toml,[mcp_servers.warmbuild]withurl = "https://warmbuild-mcp-dev.mikitahimpel.com/mcp", thencodex mcp login warmbuild.
Tools: list_repositories; latest_ci_report for a repository, branch or pull request (where the minutes went, cache hits and misses, failed and flaky tests, findings);
why_cache_miss for a target; tests (slow, flaky or failing); pr_test_selection (what a pull request ran and why); and build_insights.
Results are short: names, durations and counts, never source or logs. Access lasts an hour and renews for 30 days.
The CI report
After each job, the Action’s post step reads the build logs and result bundles Xcode left behind and uploads a summary. The job summary on GitHub shows it at once; the dashboard’s CI report tab keeps the history per repository.
- Where the minutes go: the median job split into compile, cache, asset catalogs, link, tests, waiting for the Simulator, and other work. Time that several kinds of work share is split between them, so the parts add up to the job.
- Trend: one column per job, so a regression shows up as a step.
- Findings: patterns across recent jobs (Simulator boot before tests, vector images rendered at every scale, dSYMs nobody uploads) with an estimate of what each would save and what the estimate assumes.
- Latest job: targets by compile time, the slowest tasks and the slowest tests.
What is uploaded: task names, target names, file names of slow tasks, test identifiers, durations, counts and sizes. Never source code, command lines or logs. Set report: false on the Action to turn it off.
Test selection
With select-tests: true, pull requests run only the tests their change can reach. Pushes to the
default branch run everything, so a test that selection skipped and that would have failed is found one push
later; the dashboard counts those misses and names the pull request.
- The Action exports the arguments in
$WARMBUILD_ONLY_TESTING; pass them toxcodebuild test. Empty means run everything. - Changes to the project, a package manifest, resources or many files at once run everything.
select-tests-alwayslists tests to run on every pull request;select-tests-ignorelists paths that never affect tests.- On a Mac,
xcodebuild test … $(warmbuild tests --changed)does the same for your local diff.
Write rules
A CI run writes to the cache when a rule matches its repository and ref; every other run only reads. With no
rules, each repository’s default branch writes. Pull requests and Macs never write. Account admins edit the
rules on the dashboard’s Write rules tab: @default is the repository’s default branch and
* matches anything, e.g. owner/app on refs/heads/release/*.
Security and privacy
- What is stored: compiler outputs, addressed by their hash, and the CI report’s build metadata. No source code, no logs.
- Who can read it: runs and Macs of the same account. Each account has its own keys; nothing is shared or deduplicated across accounts.
- Sign-in: on CI, the job’s short-lived GitHub OIDC token (hence
id-token: write). On a Mac, GitHub’s device sign-in; the token stays in the macOS Keychain. Self-hosted runners without OIDC can use an API token from the dashboard. - Integrity: every object is checked against its hash on read; a mismatch counts as a miss.
- Retention: a cache entry is served for at most 7 days after it was written.
When something is off
- No hits on CI: the default branch has to run once with the Action before pull requests can read anything. Check the job summary for hits and misses.
- Few hits on a Mac: use the same Xcode, configuration and destination as CI; a branch far from the default branch shares less.
- The cache is unreachable or the account is not approved: builds run without it, as before.
warmbuild statuson a Mac says which. - Turn it off: remove the step on CI;
warmbuild stopon a Mac.
Case note: billet, November 2026
billet is an iOS app with about 100k lines of Swift, built with Xcode 27.2 on GitHub-hosted macOS runners. One data point, with how it was measured, so you can judge it; your own numbers appear in the CI report after your first jobs.
| What | Result | How it was measured |
|---|---|---|
| Build step | 521 s → 212 s | One pull request, built without the cache and then with it. The xcodebuild build step only, not the whole job. Hosted runners vary from run to run. |
| Cache lookups | 97% hits | The same build with the cache: hits over all cache lookups. |
| Tests on pull requests | 34 of 505 | Median unit tests selected per commit, replaying 100 default-branch commits with the shipped selection; 24 of the 86 commits that touched code ran everything. |
What this does not say: how a different app, a different runner type or a whole job would change. The report shows where your job’s time goes, so you can see which part the cache can touch.