# simctl command reference

Lookup tables for `xcrun simctl`. Workflows and the simulator-versus-hardware
boundary are in [the main skill](../SKILL.md). `<dev>` below means a UDID, or
`booted` when exactly one simulator is running. `xcrun simctl help <command>`
prints the authoritative usage for the installed Xcode.

## Devices

| Command | What it does |
|---|---|
| `list [devices\|devicetypes\|runtimes\|pairs] [term\|available]` | Lists items; `-j` or `--json` for JSON; the `available` term hides unusable ones |
| `create <name> <device-type-id> [<runtime-id>]` | Makes a device and prints its UDID; without a runtime, the newest compatible one is used |
| `clone <dev> <new-name>` | Copies a device, installed apps and all |
| `delete <dev>\|unavailable\|all` | Removes devices; `unavailable` means devices the current Xcode SDK cannot run (for example, a missing runtime) |
| `rename <dev> <new-name>` | Renames |
| `erase <dev>\|all` | Wipes content and settings, keeps the device |
| `boot <dev>` | Starts the device's runtime |
| `bootstatus <dev> [-b]` | Waits until boot completes; `-b` starts the boot first if needed |
| `shutdown <dev>\|all` | Stops devices |
| `upgrade <dev> <runtime-id>` | Moves a device to a newer runtime |
| `pair <watch-dev> <phone-dev>` / `unpair <pair-id>` | Links a watchOS simulator to an iOS one, or removes the link |

Identifiers look like `com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro`
for device types and `com.apple.CoreSimulator.SimRuntime.iOS-26-0` for
runtimes. `create` and `upgrade` take these identifier strings; read the
exact values from `list devicetypes` and `list runtimes`.

## Apps

| Command | What it does |
|---|---|
| `install <dev> <path-to.app>` | Installs a simulator build; the device must be booted and the path is an `.app` folder, not an `.ipa` |
| `uninstall <dev> <bundle-id>` | Removes the app and its data |
| `launch [--console\|--console-pty] [--terminate-running-process] [--stdout=<file>] [--stderr=<file>] <dev> <bundle-id> [args...]` | Starts the app; `--terminate-running-process` stops a running copy first |
| `terminate <dev> <bundle-id>` | Stops the running app |
| `spawn <dev> <binary> [args...]` | Runs any executable inside the simulator, which is how `log stream` runs |
| `get_app_container <dev> <bundle-id> [app\|data\|groups\|<group-id>]` | Prints a container path; `groups` lists every App Group container |
| `appinfo <dev> <bundle-id>` | Prints information from the app's Info.plist |

In scripts, prefer `bootstatus <dev> -b` followed by `install`. If a script
calls `boot` by itself, add `bootstatus <dev>` before any install, launch,
push or location command.

### Arguments and environment

- Extra words after the bundle ID reach the app as `CommandLine.arguments`.
- Force a language and region for one run:
  `xcrun simctl launch "$SIM" org.example.pantry -AppleLanguages "(ko)" -AppleLocale "ko_KR"`.
- Environment variables for the launched app are taken from the calling
  shell with a `SIMCTL_CHILD_` prefix, which simctl strips:

```bash
SIMCTL_CHILD_API_STAGE=beta SIMCTL_CHILD_VERBOSE_SYNC=1 xcrun simctl launch "$SIM" org.example.pantry
```

`spawn` is for arbitrary processes such as `log stream`; it is not the way to
start an installed app with custom environment variables.

## Simulation

| Command | What it does |
|---|---|
| `push <dev> [<bundle-id>] <payload.json\|->` | Delivers a local remote-notification payload; the bundle ID may be omitted when the payload names it in `Simulator Target Bundle` (an explicit argument wins) |
| `openurl <dev> <url>` | Opens a URL as if tapped |
| `location <dev> set\|clear\|list\|run\|start [...]` | Simulated location |
| `privacy <dev> grant\|revoke\|reset <service> [<bundle-id>]` | Permission state; some changes terminate the running app |
| `keychain <dev> add-root-cert\|add-cert\|reset [path]` | Adds a trusted root or other certificate, or resets the keychain |
| `status_bar <dev> override\|clear\|list [flags]` | Status bar overrides |

## Push payloads

The payload follows the APNs format:

```json
{
  "Simulator Target Bundle": "org.example.pantry",
  "aps": {
    "alert": {
      "title": "Delivery window changed",
      "subtitle": "Order 1182",
      "body": "Your groceries now arrive between 6 and 7 pm."
    },
    "badge": 2,
    "sound": "default",
    "category": "DELIVERY_UPDATE",
    "thread-id": "order-1182",
    "interruption-level": "time-sensitive"
  },
  "orderID": "1182"
}
```

simctl checks three things: the top level is a JSON object, it contains a
valid `aps` dictionary, and the whole payload is 4096 bytes or less. Only
ordinary application remote notifications are supported; VoIP, complication,
File Provider and other push types are rejected.

## Location

- Built-in scenarios include "Freeway Drive", "City Run", "City Bicycle Ride"
  and "Apple" (a fixed point at the company campus); `list` prints what the
  local runtime offers.
- `start` needs at least two `lat,lon` waypoints and accepts
  `--speed=<m/s>` (20 m/s when omitted) plus either `--distance=<meters>`
  between updates or `--interval=<seconds>` between updates (1 s when neither
  is given). Use `-` to read waypoints from stdin, one per line. Coordinates
  use `.` for decimals and `,` between latitude and longitude.
- `run` accepts scenario names only. A stored GPX route is played through
  Xcode's Debug > Simulate Location.

## Media and capture

| Command | Notes |
|---|---|
| `io <dev> screenshot [--type=png\|jpeg\|tiff\|bmp\|gif] [--mask=ignored\|alpha\|black] <file>` | PNG unless a type is given; `-` writes to stdout |
| `io <dev> recordVideo [--codec=h264\|hevc] [--mask=ignored\|black] [--force] <file>` | HEVC by default; stop with Ctrl+C; `--force` overwrites an existing file |
| `addmedia <dev> <path> [path...]` | Imports photos, Live Photos (photo plus video), videos and vCard contacts; PNG, JPEG, GIF, MOV, MP4 and `.vcf` all work |

Mask policies for non-rectangular screens:

- `ignored` (default): the whole rectangular framebuffer.
- `alpha`: pixels outside the screen shape become transparent, so a PNG keeps
  the rounded corners. Video does not support it and renders the mask black.
- `black`: pixels outside the screen shape are black; fine for JPEG.

`recordVideo` prints "Recording started" to stderr once the first frame is
captured, which scripts can wait for.

## JSON with jq

```bash
xcrun simctl list devices booted --json |
  jq -r '[.devices[][] | select(.state == "Booted") | .udid] | first'

xcrun simctl list devices --json |
  jq -r --arg rt com.apple.CoreSimulator.SimRuntime.iOS-26-0 \
    '.devices[$rt][] | select(.name == "Checkout iPad") | .udid'

xcrun simctl list runtimes --json |
  jq -r '.runtimes[] | select(.isAvailable) | "\(.name)\t\(.identifier)"'

xcrun simctl list devices --json |
  jq -r --arg id "$SIM" '.devices[][] | select(.udid == $id) | .state'
```

In order: the UDID of the first booted device; a UDID by name and runtime;
installed runtimes that can be used, with their identifiers; and the state of
one device.

## Privacy services

| Service | Scope |
|---|---|
| `all` | Every service. `grant` and `revoke` still need a bundle ID; `reset` can omit it |
| `calendar` | Calendar events (EventKit) |
| `reminders` | Reminders (EventKit) |
| `contacts-limited` | Basic contact details |
| `contacts` | Full contact records |
| `location` | Location while in use |
| `location-always` | Location at all times |
| `photos-add` | Adding to the library only |
| `photos` | Full photo library |
| `media-library` | Music and media library |
| `microphone` | Audio input |
| `motion` | Motion and fitness data (Core Motion) |
| `siri` | Using the app with Siri |

## Status bar flags

| Flag | Accepted values |
|---|---|
| `--time` | Any string, for example `9:41`; a valid ISO date also sets the date |
| `--dataNetwork` | `wifi` or `hide`; cellular types `3g`, `4g`, `lte`, `lte-a`, `lte+`, and the 5G family `5g`, `5g+`, `5g-uc`, `5g-uwb` |
| `--wifiMode` | `searching`, `failed`, `active` |
| `--wifiBars` | 0 to 3 |
| `--cellularMode` | `notSupported`, `searching`, `failed`, `active` |
| `--cellularBars` | 0 to 4 |
| `--operatorName` | Any string; `''` for none |
| `--batteryState` | `charging`, `charged`, `discharging` |
| `--batteryLevel` | 0 to 100 |

A preset for store screenshots:

```bash
xcrun simctl status_bar "$SIM" override \
  --dataNetwork wifi --wifiMode active --wifiBars 3 \
  --cellularMode active --cellularBars 4 --operatorName '' \
  --batteryState charged --batteryLevel 100 --time "9:41"
```

## Troubleshooting

**Device stuck while booting** (boot fails because the device is already in
the Booting state):

1. `xcrun simctl shutdown <dev>`, then `erase <dev>`, then `boot <dev>`.
2. Still failing: delete the device and create a new one.
3. Last resort: `xcrun simctl shutdown all` and remove
   `~/Library/Developer/CoreSimulator/Caches`.

**Runtime missing** ("Invalid runtime", or nothing under `list runtimes`):
check `xcrun simctl list runtimes`, then install one with
`xcodebuild -downloadPlatform iOS` or from Xcode > Settings > Components.
Runtimes can be larger than 5 GB, so CI images should have them preinstalled.

**CoreSimulator cache damage** shows up as boot failures, a stale
`simctl list`, or "Unable to determine simulator device status". Full reset:

```bash
xcrun simctl shutdown all
xcrun simctl erase all
rm -rf "$HOME/Library/Developer/CoreSimulator/Caches"
launchctl kickstart -k "gui/$UID/com.apple.CoreSimulator.CoreSimulatorService" || true
```

Afterwards, custom devices may need to be created again; Xcode recreates its
default set by itself.

**Boot fails from the Shutdown state** ("Unable to boot device in current
state: Shutdown"): usually the runtime is missing or damaged. Look for
"(unavailable)" next to it in `list runtimes` and download it again with
`xcodebuild -downloadPlatform iOS`.
