Bei Remote-CI tritt gelegentlich ein schwer reproduzierbarer Fehler auf: Xcode hat den Build bereits abgeschlossen, doch der iOS-Simulator bleibt bei Booting hängen. Oder das Gerät wird als Shutdown angezeigt, während der Installationsbefehl weiterhin meldet, dass keine Verbindung zum Dienst hergestellt werden konnte. Ein Neustart des Cloud-Macs behebt das Problem oft vorübergehend, beseitigt aber zugleich alle Spuren des Fehlers und erklärt nicht, warum er erneut auftritt. Zuverlässiger ist es, Runtime, Gerätestatus und Job-Parallelität getrennt zu untersuchen.
Zuerst die Fehlerursache einer Ebene zuordnen
Simulatorfehler lassen sich meist einer von drei Ebenen zuordnen: Die Xcode-Runtime ist nicht verfügbar, das Geräteverzeichnis ist beschädigt oder mehrere Jobs greifen gleichzeitig auf dasselbe Gerät zu. Erfasse zunächst den Xcode-Pfad und die Geräteliste, statt sofort erase all auszuführen.
set -euo pipefail
xcode-select -p
xcodebuild -version
xcrun simctl list runtimes
xcrun simctl list devices
xcrun simctl list devices unavailable
Wird eine Runtime als unavailable angezeigt, prüfe zuerst, ob die aktuell ausgewählte Xcode-Version die vom Projekt benötigte iOS Runtime enthält. Sind mehrere Xcode-Versionen installiert, sollte das Developer-Verzeichnis explizit festgelegt und die Prüfung anschließend wiederholt werden:
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcrun simctl list runtimes
Die Runtime-ID darf nicht aus dem Namen der Xcode-App abgeleitet werden. Automatisierungsskripte sollten die tatsächlich verfügbaren Einträge aus simctl list -j lesen und Runtimes mit isAvailable=false ablehnen.
Erst Beweise sichern, dann destruktiv bereinigen
Wenn ein Gerät hängen bleibt, sichere die JSON-Liste und die neuesten Protokolle. So lässt sich auch nach einem späteren Neuaufbau des Geräts noch unterscheiden, ob der CoreSimulator-Dienst gestört war, der Startprozess beendet wurde oder die App selbst abgestürzt ist.
mkdir -p artifacts/simulator
xcrun simctl list -j > artifacts/simulator/list.json
xcrun simctl diagnose \
> artifacts/simulator/diagnose.txt 2>&1 || true
xcrun simctl spawn booted log show \
--last 5m \
--style compact \
--predicate 'process == "SpringBoard" OR process == "launchd_sim"' \
> artifacts/simulator/boot.log 2>&1 || true
Wenn derzeit kein booted Gerät vorhanden ist, darf der letzte Befehl fehlschlagen. Das Skript sollte die ersten beiden Ergebnisse dennoch aufbewahren. Vor der Archivierung müssen die Protokolle außerdem bereinigt werden, damit Arbeitsverzeichnisse, Token-Parameter oder Pfade zu Signaturmaterial nicht in langfristig gespeicherte CI-Artefakte gelangen.
erase,deleteund das direkte Löschen von Geräteverzeichnissen sind destruktive Aktionen. Werden sie vor der Beweissicherung ausgeführt, lautet das Ergebnis meist nur „nach erneutem Versuch behoben“ statt einer behebbaren Ursache.
Für jeden Job ein eigenes Device Set erstellen
Das standardmäßige Device Set liegt im Benutzerverzeichnis und ist für alle parallelen Jobs zugänglich. Führt ein Job shutdown all aus, kann ein anderer sein Gerät mitten im Test verlieren. Die Lösung sind nicht mehr Wiederholungsversuche, sondern ein eigenes Verzeichnis für jeden Job.
JOB_KEY="${CI_JOB_ID:-local}-$$"
DEVICE_SET="$PWD/.simulators/$JOB_KEY"
mkdir -p "$DEVICE_SET"
DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-16"
RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-18-0"
UDID="$(
xcrun simctl --set "$DEVICE_SET" create \
"ci-$JOB_KEY" "$DEVICE_TYPE" "$RUNTIME_ID"
)"
xcrun simctl --set "$DEVICE_SET" boot "$UDID"
xcrun simctl --set "$DEVICE_SET" bootstatus "$UDID" -b
Gerätetyp und Runtime im Beispiel veranschaulichen nur das Parameterformat. Die tatsächlichen Werte müssen aus der zuvor erzeugten JSON-Liste ausgewählt werden. Schlägt die Geräteerstellung fehl, darf nicht unbemerkt auf das standardmäßige Device Set zurückgefallen werden. Andernfalls versagt die Isolation genau dann, wenn sie am dringendsten benötigt wird.
Nur das eigene Verzeichnis bereinigen
Beende nach Abschluss des Jobs zuerst die eigenen Geräte und lösche anschließend das zugehörige Verzeichnis. Auf gemeinsam genutzten Runnern darf kein globales delete all ausgeführt werden.
xcrun simctl --set "$DEVICE_SET" shutdown all || true
rm -rf "$DEVICE_SET"
Die Startprüfung auf die Anwendungsebene erweitern
bootstatus -b bestätigt lediglich, dass das System vollständig gestartet ist. Es sagt nicht aus, ob sich die zu testende App installieren und ausführen lässt. Eine vollständige Smoke-Prüfung umfasst mindestens vier Punkte:
| Prüfung | Befehl oder Signal | Bei einem Fehler sichern |
|---|---|---|
| Gerätestart | bootstatus -b |
Diagnose- und Startprotokolle |
| App-Installation | simctl install |
App-Pfad, Exitcode |
| App-Start | simctl launch |
Bundle Identifier, Prozessausgabe |
| Testbereitschaft | Statusdatei oder Health Probe | Deadline und letzter Status |
APP_PATH="$PWD/build/Sample.app"
BUNDLE_ID="com.example.Sample"
xcrun simctl --set "$DEVICE_SET" install "$UDID" "$APP_PATH"
xcrun simctl --set "$DEVICE_SET" launch \
--console-pty "$UDID" "$BUNDLE_ID"
Die Pipeline sollte für die Startphase ein eindeutiges Zeitlimit festlegen. Bei einem Timeout sind zuerst die Protokolle zu erfassen und erst danach die Geräte herunterzufahren. Eine feste Zahl bedingungsloser Wiederholungsversuche verlängert bei einem deterministischen Runtime-Fehler lediglich die Wartezeit.
Eine gestufte Wiederherstellungsreihenfolge festlegen
Wiederherstellungsmaßnahmen sollten mit dem Schritt beginnen, der die geringsten Auswirkungen hat:
- Erneut prüfen, ob
DEVELOPER_DIR, Runtime und Gerätetyp zueinander passen. - Das Gerät des aktuellen Jobs herunterfahren und neu starten.
- Das fehlerhafte Gerät innerhalb desselben isolierten Device Sets löschen und neu erstellen.
- Nachweislich ungültige Einträge aus
simctl list devices unavailablebereinigen. - Einen Neustart der CoreSimulator-Dienste oder des gesamten Rechners nur erwägen, wenn keine anderen Jobs laufen.
Wenn jedes Mal der fünfte Schritt erforderlich ist, sollte geprüft werden, ob der Runner weiterhin das gemeinsam genutzte standardmäßige Device Set verwendet, ob der Speicherplatz nahezu ausgeschöpft ist und ob nach abgebrochenen Jobs Simulator-Unterprozesse zurückbleiben. Für Remote-Jobs auf RunnerVM gelten dieselben Grundsätze: Nach der Prüfung der aktuell verfügbaren Konfigurationen in der Konsole muss der Pfad des Device Sets in den Job-Lebenszyklus eingebunden werden, statt den Simulatorstatus als dauerhafte Ressource des Rechners zu behandeln.
Fehler vergleichbar machen
Schreibe abschließend Xcode-Version, Runtime-ID, Gerätetyp, UDID, Startdauer und Fehlerphase in ein strukturiertes Ergebnis. Erst durch die Aggregation aufeinanderfolgender Fehler nach diesen Feldern wird sichtbar, ob das Problem auf eine bestimmte Runtime, eine Geräteklasse oder bestimmte parallele Jobs konzentriert ist.
Eine zuverlässige Simulator-Wiederherstellung endet nicht damit, dass sich die Oberfläche endlich öffnet. Sie ist erst abgeschlossen, wenn sich die Befehle reproduzierbar ausführen lassen, die App installiert und gestartet werden kann, die Fehlerbelege archiviert wurden und der private Job-Status bereinigt ist. Tritt das Problem erneut auf, kann die Untersuchung dann an einer bekannten Phase fortgesetzt werden, statt wieder von vorn zu raten.
Häufig gestellte Fragen
Soll ein im Zustand Booting hängendes Gerät zuerst gelöscht werden?
Nein. Zuerst sollten Geräteliste und Protokolle gesichert werden. Danach wird ein Ersatzgerät in einem isolierten Device Set erstellt; erase ist nur für entbehrliche Altzustände geeignet.
Dürfen parallele CI-Jobs das Standard-Device-Set gemeinsam nutzen?
Davon ist abzuraten. Ein Job kann ein Gerät herunterfahren oder löschen, das ein anderer gerade verwendet. Ein eigenes Device Set pro Job verhindert diese Überschneidung.
Reicht ein erfolgreicher bootstatus als Bereitschaftsprüfung?
Nein. Zusätzlich müssen die erstellte App installiert, ihre Bundle-ID gestartet und die Rückgabewerte aller Schritte ausgewertet werden.
Den nächsten Build auf einem exklusiven physischen Mac ausführen
Wählen Sie einen Cloud-Mac-Knoten passend zur Auftragsdauer und verwalten Sie Bestellungen, Verbindungsdaten und Support-Tickets in der Konsole.