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.

PieceWhat it holdsWhere it comes from
RuntimeThe guest OS build, frameworks, and system apps for one platform versionBundled with the toolchain release, or downloaded afterwards
Device typeA hardware profile: screen geometry, memory budget, and the capabilities the guest reportsShipped with the toolchain, listed by xcrun simctl list devicetypes
Simulated deviceA pairing of one device type with one runtime, holding its own UDID and data containerCreated 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 runtimes

Each 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.xcresult

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

DestinationRuntime that bootsWhat moves between runs
platform=iOS Simulator,name=iPhone 17,OS=latestNewest runtime installed on the machineAn image update that adds a runtime silently retargets the suite
platform=iOS Simulator,name=iPhone 17,OS=26.0The 26.0 runtime, or the job failsOnly 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 iOS

That 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.

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.