Warm Xcode builds for coding agents
Every fresh worktree, agent session or new CI runner starts with a cold Xcode build. Coding agents make that the normal case: Claude Code, Codex and Cursor work best in a worktree per task, often several at once, and each one compiles the whole app before it can check its first change. A shared compilation cache makes those builds warm.
TL;DR
- Each worktree gets its own DerivedData folder, and Xcode’s local caches live inside it, so a new worktree compiles everything again.
- With Warmbuild, CI fills a shared compilation cache from the default branch.
warmbuild starton the Mac maps every worktree of the repository, including ones created later, so each reads it with CI’s keys. - Paste the prompt below into your agent to set it up; you only enter one GitHub sign-in code.
- Agents without a shell read the same CI reports through the MCP server at
https://mcp.warmbuild.dev/mcp.
Why agents build cold
A worktree is a new checkout path. Xcode names the default DerivedData folder after the project and where it
is, so git worktree add ../app-fix-login gets a fresh, empty folder. Build records, module caches and
the index are all per folder; nothing from the main checkout is reused. Even
copying DerivedData across doesn’t help: it is tied to absolute paths
and file times.
Xcode’s own compilation cache is per folder too. With Xcode 26 compilation caching on, the cache is kept in each
DerivedData folder (CompilationCache.noindex). You can point every checkout at one with
COMPILATION_CACHE_CAS_PATH, but keys contain paths: in our test, a second checkout of a small app missed
every one of its own compilations against a shared cache, and hit only the SDK modules.
Parallel agents multiply it. Three agents in three worktrees compile the same unchanged modules three times, on the same Mac, competing for the same cores. And when they push, every CI job starts on a new runner with nothing at all.
How a shared cache helps
- CI fills it. The
warmbuild/action@v1step runs beforexcodebuildon GitHub Actions. Pushes to the default branch write their compile results; pull requests only read. - The Mac reads it. The
warmbuildCLI runs a local cache server and an xcconfig that points Xcode andxcodebuildat it. A compilation whose key CI already produced is fetched instead of compiled. Laptops only read. - Every worktree matches CI.
warmbuild start, run once in any checkout, maps all worktrees of that repository, including ones created later, the way CI maps its checkout. A build in any worktree, at any path, gets CI’s keys for the files it hasn’t changed, and several worktrees can build at the same time. - One local store for all of them. The server’s store in
~/.warmbuild/storeis shared by every DerivedData folder, so what one worktree fetched, the next one has locally. - Never in the way. If the server stops, the xcconfig is emptied and builds run as plain local builds. If Warmbuild can’t be reached, the server answers from the Mac’s own cache.
warmbuild status shows the last build of all worktrees together and each worktree’s own, so you, or the
agent, can see whether a session was warm.
Set it up: paste this into your agent
Prerequisites: a Mac with Xcode 26 or later and Node 20 or later, and a repository whose GitHub account has the Warmbuild GitHub App installed (before you start). Open your agent in the repository and paste:
Set up Warmbuild (a shared Xcode compilation cache) for this repository on this Mac. Ask me before installing anything. 1. Check whether it is installed: `command -v warmbuild || ls ~/.warmbuild/bin/warmbuild`. If not, run in the repository root: `curl -fsSL https://warmbuild.dev/install.sh | sh -s -- --add-to-path` (or `brew install warmbuild/tap/warmbuild`). Use `~/.warmbuild/bin/warmbuild` if `warmbuild` is not on PATH yet.2. Run `warmbuild status`. If no account is signed in, run `warmbuild login` in the background: it prints a GitHub URL and a code, then waits up to 15 minutes. Show me both; I will enter the code. Never read or print the token.3. Run `warmbuild start` in the repository. Once is enough for every worktree, including ones created later.4. Add the instructions for future sessions: append https://raw.githubusercontent.com/warmbuild/action/v1/skills/warmbuild/AGENTS-snippet.md to AGENTS.md (CLAUDE.md for Claude Code). Show me the diff; don't commit it.5. Build once with `XCODE_XCCONFIG_FILE="$HOME/.warmbuild/warmbuild.xcconfig" xcodebuild …` (keep -derivedDataPath out of /tmp), then run `warmbuild status` and tell me the hit rate. If it is low, run `warmbuild doctor`.6. If .github/workflows does not use warmbuild/action yet, show me the change (`uses: warmbuild/action@v1` before xcodebuild, and `permissions: id-token: write`) but don't commit it. If a warmbuild command fails, run `warmbuild stop` and build without it.Every command in it is in the Warmbuild skill. The cache stays useful only once CI fills it: until a default-branch job with the Action has run, builds miss.
Set it up by hand
In CI, add the step before xcodebuild:
permissions: id-token: write contents: readjobs: build: runs-on: macos-26 steps: - uses: actions/checkout@v5 - uses: warmbuild/action@v1 - run: xcodebuild build -scheme App -destination 'generic/platform=iOS Simulator'On the Mac, in the repository:
curl -fsSL https://warmbuild.dev/install.sh | sh # installs, signs in, runs warmbuild startwarmbuild statusThen tell the agent about it. For Claude Code, install the skill (for one repository, use .claude/skills/warmbuild/):
mkdir -p ~/.claude/skills/warmbuild && curl -fsSL \ https://raw.githubusercontent.com/warmbuild/action/v1/skills/warmbuild/SKILL.md \ -o ~/.claude/skills/warmbuild/SKILL.md
For Codex, Cursor and others, paste the
AGENTS.md snippet
into the repository’s AGENTS.md. Full details: the Mac CLI and
coding agents in the docs.
What the agent then does
With the skill or snippet in place, an agent working in any worktree:
# build with the cache; reaches Swift package targets tooXCODE_XCCONFIG_FILE="$HOME/.warmbuild/warmbuild.xcconfig" xcodebuild build -scheme App \ -destination 'generic/platform=iOS Simulator' warmbuild status --json # hits, misses, hit rate, per worktreewarmbuild doctor # why a build hit little, with the fix # only the tests the diff against the default branch reaches (empty output: all tests)xcodebuild test -scheme App -destination 'platform=iOS Simulator,name=iPhone 17' $(warmbuild tests --changed)
It can also read CI’s results from the terminal: warmbuild builds for recent jobs,
warmbuild report for where a job’s time went, warmbuild why-miss <target> for a target that
stopped hitting, and warmbuild tests --flaky. All take --json.
CI reports over MCP
Agents without a shell (Claude on the web and desktop, cloud agents) read the same CI report data through Warmbuild’s
remote MCP server: https://mcp.warmbuild.dev/mcp, Streamable HTTP, signed in with the same GitHub account
as the dashboard. Every tool only reads, and results are names, durations and counts, never source or logs.
| Tool | Returns |
|---|---|
list_repositories | Repositories with CI reports you can read |
latest_ci_report | The newest job for a repository, branch or pull request: where the minutes went, cache hits and misses, failed and flaky tests, findings |
why_cache_miss | A target’s hits and misses over recent jobs, and what changed since the last job without misses |
tests | Slow, flaky or failing tests |
pr_test_selection | What a pull request’s test selection ran, and why |
build_insights | The median job, findings with estimated savings, cache hit rate |
- Claude Code:
claude mcp add --transport http warmbuild https://mcp.warmbuild.dev/mcp, then/mcpto sign in. - Claude (web and desktop): Settings → Connectors → Add custom connector, paste the URL.
- Cursor: in
~/.cursor/mcp.json,{"mcpServers": {"warmbuild": {"url": "https://mcp.warmbuild.dev/mcp"}}}. - Codex: in
~/.codex/config.toml,[mcp_servers.warmbuild]withurl = "https://mcp.warmbuild.dev/mcp", thencodex mcp login warmbuild.
A useful first question: “Why did the last CI job on my branch miss the cache?”, which the agent answers with latest_ci_report and why_cache_miss.
Limits worth knowing
- Same build as CI. Keys match CI’s only for the same Xcode version, configuration, destination and architecture as the job that filled the cache. Changed files and code CI hasn’t built yet always compile.
- Xcode 26 and DerivedData paths. Xcode 26 puts the DerivedData path into Swift keys despite the maps, so Swift compilations hit only when DerivedData is at the same path as on CI; Clang modules hit anywhere. Xcode 27 doesn’t have this limit.
- Not under /tmp. Keep worktrees and
-derivedDataPathout of/tmpand/private/tmp; there, compilations that use the project’s C modules miss. A folder in your home directory, or Xcode’s default DerivedData, hits. - No spaces in worktree paths. Xcode’s prefix maps are space-separated, so such a worktree can’t be mapped.
warmbuild status --jsonlists it underunmappable. #filePathis/tmp/warmbuild/src/…in every worktree, and that link points at one checkout at a time. Tests that read files next to their sources through it should runwarmbuild linkfirst, or find their files another way.- macOS only. Agents in Linux cloud sandboxes can’t run Xcode at all; they can still read CI reports over MCP.
Sources
- Warmbuild, skills/warmbuild/SKILL.md and AGENTS-snippet.md: every command on this page.
- Warmbuild docs: the Mac CLI, coding agents, the MCP server.
- Apple, Xcode 26 release notes, “Compilation caching”.
- Measurements behind the shared-cache example: Warmbuild’s
fixtures/iosapp, Xcode 27.2, 15 November 2026 (details in Xcode 26 compilation caching on GitHub Actions).