Llim.run

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:

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 list

Each 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 version

Pin 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 beta

lim 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 26

Forget the preference

Remove the preference, and the sandbox goes back to the node default:

lim xcode version unset

Select 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).

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:

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

ToolsDefault line
Node.js, npm, npxNode 22 and 24, default 22; npm and npx ship with Node
pnpm10; 9 and 11 are also preinstalled
Yarn1; 4 is also preinstalled
Bun, bunx1
Ruby, RubyGems3.3
Bundler4
CocoaPods1, including the cocoapods-patch plugin
CMake, ctest, cpack3
JavaJetBrains Runtime 21; Corretto 21 is available for Gradle vendor detection
Flutter and its Dart SDKFlutter 3.44
Mint0.18
XcodeGen2
xcbeautify3
zsign1, 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 tools

mise 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

hammer

Build with Xcode

Sync, build, and install with the versions you selected.

key-round

Sign and distribute

Ship a release build from a GA Xcode to App Store Connect.