Xcode and developer tool versions
By default, an Xcode sandbox builds with its node's default Xcode and a set of preinstalled developer tools. Xcode is selected per workspace, other tools through mise. To carry the workspace itself across sandboxes, use disk snapshots.
Choose the Xcode version
A sandbox builds with its node's default Xcode unless you select another installed version. The fleet carries one released (GA) Xcode per major and, while Apple is seeding one, one beta. Two selectors cover them:
- A bare major (
27) binds the newest GA release of that major on the node, never a beta. It follows Apple's point releases on its own. Use it for App Store builds. - A major.minor (
27.1) pins that exact version. This is how you pick a beta.
The preference is stored per workspace, the way a version manager remembers a version. A workspace is your git repository or worktree, or a directory assigned with lim set-workspace-dir.
List the installed versions
Print the versions the sandbox can build with:
lim xcode version listEach row shows the bound marker (* for the one in use), the Select value to type, the channel (ga or beta), and the full version. --quiet prints only the Select values, one per line. For example, on a node with three installed versions the Select values are 26, 27, and 27.1, and the last row reads 27.1 beta (27A9269). The versions on your node may differ.
To see which Xcode the sandbox has selected, and the workspace preference when the two differ:
lim xcode versionPin a version for the workspace
Set the preference with lim xcode use or lim xcode version set. Both remember the selector and switch the existing sandbox; with no sandbox, they save the preference for the next one. lim xcode build, test, rbe, and new sandboxes all use it:
lim xcode use xcode@27 # the newest GA Xcode 27
lim xcode version set 27.1 # or pin the 27.1 betalim xcode use xcode@27 is equivalent to lim xcode version set 27 and does not sync or write mise configuration. You can combine it with tool requests, such as lim xcode use xcode@27 node@24; only the other tools go into mise. --cwd applies only to mise tools, and --workspace chooses the Limrun workspace whose preference is set.
Override the version for one command
Pass --xcode-version <major|major.minor> to lim xcode build, create, test, or rbe. It overrides the preference for that command and leaves the preference unchanged:
lim xcode build . --xcode-version 26Forget the preference
Remove the preference, and the sandbox goes back to the node default:
lim xcode version unsetSelect the version from the SDK
From the TypeScript SDK, read the installed versions and bind one on the sandbox client:
const { installed } = await xcode.getXcode();
for (const x of installed) console.log(x.version, x.channel); // for example "27.0 ga", "27.1 beta"
await xcode.setXcode('27'); // GA lane: the newest released Xcode 27
await xcode.setXcode('27.1'); // beta lane: this exact version; pick one lane or the other
const { bound } = await xcode.getXcode();
if (bound.channel !== 'ga') throw new Error('App Store uploads need a released Xcode');How switching works
The sandbox has one Xcode selected at a time, and the workspace preference wins. When a build finds the sandbox on another Xcode, because a colleague switched it or a fresh sandbox came up on the node default, it says so and switches before building. Every build prints the Xcode it runs with, such as Building with Xcode 27.1 (27A9269).
- Switching Xcode invalidates build state produced with the other version, so the next build starts cold. For disk snapshots, use a separate key per Xcode lane, such as
myapp-27andmyapp-27.1: archives are stored per key, and a restore under a different Xcode is wiped. - The switch is refused while a build, command, sync, or
lim xcode rbestack is running. Stop them first (lim xcode rbe --stop).lim xcode use xcode@<version>keeps the preference in that case, and the next command retries the switch. - Asking for a version the node does not have fails with the available list, and
lim xcode use xcode@<version>records no preference for it. - A major.minor pin lasts until the fleet retires that version. Then
lim xcode buildfails with the daemon's message plus a hint to runlim xcode version set 27orlim xcode version unset. - When a beta becomes GA, the release replaces the beta under the same major.minor selector, with one cold build. The bare major follows the newest released Xcode of its major, so it moves to 27.1 as soon as 27.1 is GA on the node.
- Simulators keep running the fleet's default runtime.
lim xcode testwith a non-default Xcode warns and proceeds: XCTest bundles built by the selected Xcode run against the frameworks of the simulator's default runtime, which works for most suites but is not guaranteed. - Apple rejects App Store uploads built with a beta Xcode. Keep
--upload-to-appstoreon a bare-major pin (27, not27.1), which only ever binds a released Xcode. In the SDK, gate the publish onchannel === 'ga', not onbetaSeed: Apple's 27.1 seed ships without a seed number.
Reuse the workspace across sandboxes
A sandbox's workspace, including dependency installs and build products, is lost when the instance terminates. A disk snapshot saves it under a key and restores it into a later sandbox, so the next build starts warm. Create the sandbox with --snapshot-key, and delete it with --wait-snapshot after a successful build. Disk snapshots covers keys, branch fallbacks, and the SDK. Use a separate key per Xcode version, because a restore under a different Xcode is wiped.
Select developer tools
Select tool versions in the sandbox after your project has synced, then install them and check the result:
lim xcode use node@24 pnpm@10 [email protected]
lim xcode tools install
lim xcode tools
lim xcode build .The three commands do different jobs:
lim xcode useselects tools in an existing sandbox after a project sync. Mise installs a requested version if needed. Explicit sandbox selections take precedence over project requests and apply to later commands.lim xcode tools installsyncs the project and runsmise installin the sandbox, installing the selections from synced project files.--no-syncinstalls the current selections without syncing. It never creates or replaces an instance.lim xcode toolsinspects an existing sandbox without syncing or creating an instance.--syncuploads local changes first.
All three take --cwd apps/mobile for a nested project and --id <xcode-instance-id> to choose a sandbox, and all three need an existing sandbox. lim xcode use xcode@<version> is the exception: without a sandbox it saves the Xcode preference for the next one.
Each lim xcode run and lim xcode build resolves the mise environment (mise env --json) once at the start. Dependency installation, project generation, builds, and their child processes all use that environment.
Project requests and compatibility lines
Limrun imports [tools] declarations from synced project mise files. Project requests take precedence over the detected package-manager major and Limrun's image defaults, and deeper project files override parent files within the synced directory. Your personal mise configuration on the client is not read or forwarded.
Limrun guarantees compatibility lines, so image updates can deliver newer releases within a line:
| Tool request | Sandbox compatibility line |
|---|---|
node = "24.5.0" | 24 |
pnpm = "10.12.1" | 10 |
ruby = "3.3.7" | 3.3 |
python = "3.13.6", go = "1.24.4" | 3.13, 1.24 |
flutter = "3.44.6", dart = "3.12.2" | 3.44, 3.12 |
mint = "0.18.0" | 0.18 |
Ruby, Python, Go, Flutter, Dart, and tools below version 1 keep major.minor; other numeric versions keep their major. Client patch pins and mise.lock do not freeze the image's release. latest keeps its mise meaning and opts out of a fixed line. For any other version string, run mise in the sandbox directly; see Pin an exact version.
Preinstalled tools
The image provides these tools:
| Tools | Default line |
|---|---|
| Node.js, npm, npx | Node 22 and 24, default 22; npm and npx ship with Node |
| pnpm | 10; 9 and 11 are also preinstalled |
| Yarn | 1; 4 is also preinstalled |
| Bun, bunx | 1 |
| Ruby, RubyGems | 3.3 |
| Bundler | 4 |
| CocoaPods | 1, including the cocoapods-patch plugin |
| CMake, ctest, cpack | 3 |
| Java | JetBrains Runtime 21; Corretto 21 is available for Gradle vendor detection |
| Flutter and its Dart SDK | Flutter 3.44 |
| Mint | 0.18 |
| XcodeGen | 2 |
| xcbeautify | 3 |
| zsign | 1, with Limrun's signing fixes |
Xcode, Apple SDKs, and simulator runtimes are managed separately from mise. Select Xcode with lim xcode use xcode@27 as described above. Mise itself and Limrun's runtime helpers are part of the image.
First installation
The first sandbox operation that reads a project mise file runs mise install before exporting its environment. This happens once per sandbox, even when later files or tool selections change. Without a project mise file, commands keep using the image defaults.
A failed or cancelled first installation stops that operation. Retry explicitly with lim xcode tools install, which also installs later tool selections. Select another Java vendor with a request such as java@corretto-17; upstream support for macOS and the selected runtime still applies. The managed CocoaPods resolver uses the selected CocoaPods tool; a project Gemfile does not select its gems.
Environment the build sees
NODE_BINARY points to the Node that mise selected, in both runs and builds. For React Native and Expo builds, Limrun rewrites ios/.xcode.env.local with that binary and the selected tool PATH, so Xcode build phases use the same tools as dependency installation. Do not copy a client Node path into this file. The selected Java and Flutter installations also set JAVA_HOME and FLUTTER_ROOT.
Pin an exact version
For an exact version or an explicit update, call mise inside the sandbox:
lim xcode run -- mise install [email protected]
lim xcode run -- mise use --pin [email protected]
lim xcode toolsmise install adds a version without changing the selection. mise use installs and records an explicit sandbox override for later operations in .limrun-runtime-mise.toml; it takes precedence over project tool requests. lim xcode use node@24 replaces that override with a Node 24 selection.
A running operation keeps its environment snapshot. The next run or build picks up a version change. Use mise exec when a child command in the same operation needs the new version immediately.
Tools and disk snapshots
Tools preinstalled in the image are never part of a disk snapshot. Tools you install with mise live under .limbuild-sandbox/home/.mise/ and are saved when your --snapshot-paths cover that directory (the whole workspace by default); mise then selects among compatible image installations and restored user installations.
The publication rule above still applies: a successful lim xcode build is required. Dependency installs rerun when the resolved tool versions change, and switching Xcode still invalidates restored build state.
Mise gem wrappers contain absolute paths. After a snapshot restore moves the sandbox home, reinstall an affected user gem explicitly, for example lim xcode run -- mise install --force bundler. Image gem wrappers are packaged to support relocation.
Next steps
Was this guide helpful?