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-26 runners 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: write lets the Action sign in with the job's GitHub OIDC token; there is no secret to paste.
  • Optional: select-tests: true selects the tests a pull request can reach; pushes and other events run everything. Pass $WARMBUILD_ONLY_TESTING to xcodebuild 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-budget seconds (90); run xcrun simctl shutdown all after the tests so a busy Simulator does not slow it down.
  • Optional: the mikitahimpel/warmbuild-action/test-cache step between build-for-testing and test-without-building skips test targets whose built products passed before (see the Action's README).
.github/workflows/build.yml
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/bin without 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 start offers 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 export XCODE_XCCONFIG_FILE="$HOME/.warmbuild/warmbuild.xcconfig".
  • warmbuild doctor explains a low hit rate, with the fix. The cache updates itself daily (warmbuild update now).
  • warmbuild stop turns it off.
  • More detail on the Mac setup.
Terminal
cd your-appcurl -fsSL https://warmbuild-dev.mikitahimpel.com/install.sh | sh

Coding 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).
Claude Code
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.md

MCP 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 /mcp to 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] with url = "https://warmbuild-mcp-dev.mikitahimpel.com/mcp", then codex 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 to xcodebuild test. Empty means run everything.
  • Changes to the project, a package manifest, resources or many files at once run everything.
  • select-tests-always lists tests to run on every pull request; select-tests-ignore lists 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 status on a Mac says which.
  • Turn it off: remove the step on CI; warmbuild stop on 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.

billet measurements
WhatResultHow it was measured
Build step521 s → 212 sOne 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 lookups97% hitsThe same build with the cache: hits over all cache lookups.
Tests on pull requests34 of 505Median 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.