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 start on 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

  1. CI fills it. The warmbuild/action@v1 step runs before xcodebuild on GitHub Actions. Pushes to the default branch write their compile results; pull requests only read.
  2. The Mac reads it. The warmbuild CLI runs a local cache server and an xcconfig that points Xcode and xcodebuild at it. A compilation whose key CI already produced is fetched instead of compiled. Laptops only read.
  3. 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.
  4. One local store for all of them. The server’s store in ~/.warmbuild/store is shared by every DerivedData folder, so what one worktree fetched, the next one has locally.
  5. 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:

Prompt for Claude Code, Codex or Cursor
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:

.github/workflows/build.yml
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:

Terminal
curl -fsSL https://warmbuild.dev/install.sh | sh   # installs, signs in, runs warmbuild startwarmbuild status

Then tell the agent about it. For Claude Code, install the skill (for one repository, use .claude/skills/warmbuild/):

Terminal
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:

Terminal
# 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.

MCP tools
ToolReturns
list_repositoriesRepositories with CI reports you can read
latest_ci_reportThe newest job for a repository, branch or pull request: where the minutes went, cache hits and misses, failed and flaky tests, findings
why_cache_missA target’s hits and misses over recent jobs, and what changed since the last job without misses
testsSlow, flaky or failing tests
pr_test_selectionWhat a pull request’s test selection ran, and why
build_insightsThe median job, findings with estimated savings, cache hit rate
  • Claude Code: claude mcp add --transport http warmbuild https://mcp.warmbuild.dev/mcp, then /mcp to 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] with url = "https://mcp.warmbuild.dev/mcp", then codex 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 -derivedDataPath out of /tmp and /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 --json lists it under unmappable.
  • #filePath is /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 run warmbuild link first, 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

  1. Warmbuild, skills/warmbuild/SKILL.md and AGENTS-snippet.md: every command on this page.
  2. Warmbuild docs: the Mac CLI, coding agents, the MCP server.
  3. Apple, Xcode 26 release notes, “Compilation caching”.
  4. Measurements behind the shared-cache example: Warmbuild’s fixtures/ios app, Xcode 27.2, 15 November 2026 (details in Xcode 26 compilation caching on GitHub Actions).