Simulator Runtime
A simulator runtime is the downloadable platform image a simulator boots, versioned apart from the tools that drive it. How to pin one in a test job.
A simulator runtime is the downloadable platform image a simulator boots: the operating system build, frameworks, and system apps for iOS, tvOS, watchOS, or visionOS, packaged so a Mac can run them as a guest. It ships and is versioned separately from the developer tools that create and drive simulated devices, so the toolchain on a machine and the set of runtimes available to a test job move on independent schedules.
That separation is the reason two machines with identical toolchain versions can run the same test suite against different operating system builds. A workflow that names no runtime version inherits whatever the machine happens to carry on the day the job runs.
Definition
A runtime is a bundle on disk holding a full guest system: the kernel and userland for the guest platform, the frameworks an app links against, the system applications, and the SDK-matched dyld shared cache. Older toolchain releases expanded these bundles into /Library/Developer/CoreSimulator/Profiles/Runtimes with a .simruntime extension. Recent releases distribute them as signed disk images that mount under /Library/Developer/CoreSimulator/Volumes, which is why a runtime can be present as a download and still report as unusable until it finishes installing.
A simulated device is assembled from two pieces plus the state written while it runs.
| Piece | What it holds | Where it comes from |
|---|---|---|
| Runtime | The guest OS build, frameworks, and system apps for one platform version | Bundled with the toolchain release, or downloaded afterwards |
| Device type | A hardware profile: screen geometry, memory budget, and the capabilities the guest reports | Shipped with the toolchain, listed by xcrun simctl list devicetypes |
| Simulated device | A pairing of one device type with one runtime, holding its own UDID and data container | Created by xcrun simctl create or by a destination that requests it |
Two version lines that move independently
Each toolchain release bundles one runtime per platform, matching the SDK it was built against. Every other version is a separate download. Apple documents the download path in Installing additional simulator runtimes, and the command line form is xcodebuild -downloadPlatform iOS for one platform or xcodebuild -downloadAllPlatforms for the full set.
Runtimes are large. A single platform runtime is measured in gigabytes, so a job that downloads one before it can test pays that transfer on every run, along with the failure modes of any large network fetch.
The inventory on a machine is printed by one command:
xcrun simctl list runtimesEach row carries a marketing version such as 26.0, a build number, a runtime identifier such as com.apple.CoreSimulator.SimRuntime.iOS-26-0, and an availability field. The identifier is the stable handle to pass to xcrun simctl create when a job builds a device explicitly.
How a job picks a runtime
Test commands select a runtime through the destination specifier. platform names the simulator family, name names the device type, and OS names the runtime version. Writing OS=latest asks for the newest runtime installed on that machine, so the resolved version is a property of the machine rather than of the repository. Writing an explicit version pins the guest system, and a machine without that runtime fails at destination resolution instead of testing against a substitute.
What changes when the runtime version changes
The guest operating system version decides which APIs exist, which are deprecated, how permission prompts are presented, and which defaults apply for locale, time zone, and appearance. A snapshot test recorded against one runtime can fail on the next one because a system control was redrawn. A test that awaits a permission alert can hang when the alert moved. These read as flaky tests until the runtime version is written into the log.
Example
This job pins the runtime version in one place, fails fast when the machine does not carry it, and runs the suite against that pinned version.
name: ios-tests
on:
push:
branches: [main]
jobs:
unit-tests:
runs-on: macos-latest
env:
SIM_DEVICE: "iPhone 17"
SIM_RUNTIME: "26.0"
steps:
- uses: actions/checkout@v4
- name: Record the runtimes this machine carries
run: xcrun simctl list runtimes
- name: Fail early when the pinned runtime is missing
run: |
xcrun simctl list runtimes | grep -q "iOS ${SIM_RUNTIME}" || {
echo "iOS ${SIM_RUNTIME} simulator runtime is not installed"
exit 1
}
- name: Test against the pinned runtime
run: |
xcodebuild test \
-scheme App \
-destination "platform=iOS Simulator,name=${SIM_DEVICE},OS=${SIM_RUNTIME}" \
-resultBundlePath TestResults.xcresultThe third step is the part that changes the failure shape. Without it, a missing runtime surfaces as an xcodebuild destination error buried in build output. With it, the job stops in seconds with a message naming the version it wanted, and the preceding step has already printed the full inventory into the log.
The two destination forms behave differently over time:
| Destination | Runtime that boots | What moves between runs |
|---|---|---|
platform=iOS Simulator,name=iPhone 17,OS=latest | Newest runtime installed on the machine | An image update that adds a runtime silently retargets the suite |
platform=iOS Simulator,name=iPhone 17,OS=26.0 | The 26.0 runtime, or the job fails | Only a commit that edits the workflow |
When a suite needs a runtime the machine does not carry, the download can be made an explicit step rather than a surprise:
- name: Download a runtime the image does not carry
run: sudo xcodebuild -downloadPlatform iOSThat step makes the cost visible. It adds a multi-gigabyte transfer to every run of the job, so the cheaper arrangement is to run on a machine image that already carries the runtime versions the test matrix names, and to keep the pinned version in one environment variable so the whole matrix moves together.
Related Terms
- Simulator runtimes available on WarpBuild macOS runners: which platform runtimes each macOS image carries and how to check from inside a job.
- Running iOS simulator tests on GitHub Actions: wiring
xcodebuild testinto a workflow, including destination pinning and result bundles. - Code signing and why simulator builds skip it: what a simulator build avoids that a device build has to satisfy.
- Installing additional simulator runtimes: Apple's reference for downloading and managing runtime versions.
- GitHub runner image definitions: the upstream macOS image READMEs listing the toolchain and simulator runtime versions in each build.
- WarpBuild preinstalled software documentation: per image software references by platform.
- WarpBuild cloud runners documentation: the runner catalog with images, sizes, and tooling notes.
- WarpBuild pricing: per minute rates by runner type.
FAQ
What is a simulator runtime?
It is the guest operating system image a simulator boots: the OS build, frameworks, and system apps for iOS, tvOS, watchOS, or visionOS, packaged so a Mac can run them. It is downloaded and versioned separately from the developer tools that create and drive simulated devices.
How do I see which simulator runtimes a machine has?
Run xcrun simctl list runtimes. Each line prints the platform, the marketing version, the build number, the runtime identifier such as com.apple.CoreSimulator.SimRuntime.iOS-26-0, and an availability field that says whether the runtime is usable or only partially installed.
Why did my simulator tests change behavior with no code change?
A destination written with OS=latest resolves to the newest runtime installed on the machine at the moment the job runs. When an image update adds a newer runtime, the same commit starts testing against a different operating system build, which moves API behavior, permission prompts, and locale defaults.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.