Le 14 mai 2026, GitHub a annoncé de futures migrations d’images pour ses runners hébergés (annonce officielle). Si votre code n’a pas changé mais que le build macOS échoue, ne relancez pas immédiatement le workflow et ne mettez pas toutes les dépendances à jour. Commencez par relever, dans Set up job, l’image réellement utilisée, l’architecture, Xcode, le SDK et l’état du cache. À court terme, revenez à un tag de runner déjà validé ; à long terme, une chaîne qui exige un outil fixe, un cache durable ou un réseau privé mérite l’évaluation d’un Mac distant auto-hébergé.

Cette semaine, votre première action est de comparer une exécution réussie et une exécution échouée avant de modifier le code. Si l’environnement est identique, examinez le commit et les entrées du workflow. Si l’environnement diffère, poursuivez l’enquête sur l’image, l’architecture et les outils.

Cet article s’adresse aux développeurs qui maintiennent des builds, tests et releases iOS ou macOS. Il concerne aussi les ingénieurs DevOps responsables du choix des runners, ainsi que les équipes plateforme qui gèrent la signature, les outils privés et les caches persistants.

Dernière vérification : 21 août 2026. Les informations sur les labels, le mappage de macos-latest et le contenu des images ont été contrôlées dans la documentation des runners hébergés par GitHub, l’annonce de migration et le dépôt officiel des images.

Le premier diagnostic oppose régression du code et dérive de l’environnement

Un message « code signing failed », « SDK not found » ou « incompatible binary » ne prouve pas que l’image a changé. Une branche peut aussi avoir reçu une modification indirecte, un verrou de dépendance peut avoir bougé, ou un service externe peut répondre différemment. La première étape consiste donc à établir une différence observable.

Dans le journal de Set up job, copiez dans un artefact interne :

  • le système macOS et sa version ;
  • le label demandé et l’image effectivement provisionnée ;
  • l’architecture rapportée par le système ;
  • la version de Xcode et le chemin du répertoire développeur ;
  • le SDK et les simulateurs visibles ;
  • les versions de Ruby, Node.js, Python, Homebrew et OpenSSL ;
  • la clé de cache restaurée ou l’indication d’un cache vide.

Le label macos-latest est une indication de sélection, pas une garantie permanente sur une version précise. Le référentiel officiel des images macOS doit être consulté avec le journal de la tâche concernée. La liste publiée décrit le contenu d’une image à un moment donné ; elle ne remplace pas la preuve de votre exécution.

Preuve observée entre le dernier succès et l’échec Hypothèse prioritaire Action immédiate
Commit différent, environnement identique Régression applicative ou test instable Comparer le diff et reproduire avec les mêmes paramètres
Image ou outil différent Dérive de l’environnement Fixer temporairement une image validée et ouvrir une vérification dédiée
Architecture différente Dépendance ou cache incompatible Inspecter les binaires et reconstruire les paquets
Xcode ou SDK différent Sélection d’outil ou compatibilité projet Choisir explicitement Xcode et vérifier les exigences du projet
Compilation réussie, archivage échoué Signature, trousseau ou profil Isoler l’étape de signature et vérifier les secrets
Test échoué avec simulateur absent Runtime non installé ou mauvais appareil Lister les destinations et adapter la matrice de test

Cette comparaison évite un piège fréquent : traiter un incident d’infrastructure comme une régression de code, puis accumuler des changements qui rendent le retour arrière impossible.

ARM et Intel : l’étiquette ne suffit pas à expliquer la panne

Le passage entre une machine ARM et une machine Intel peut rendre incompatibles des extensions natives, des gems Ruby compilées, des modules Node, des paquets Homebrew ou des artefacts issus d’un cache. Les symptômes prennent souvent la forme d’un exécutable refusé, d’une bibliothèque introuvable ou d’une compilation qui échoue pendant une étape d’installation.

Vous devez produire une chaîne de preuves en trois niveaux. Commencez par le processeur vu par le système :

uname -m
arch

Inspectez ensuite le format d’un binaire réellement utilisé :

file path/to/binary

Enfin, relevez le préfixe de Homebrew et l’emplacement des paquets :

brew --prefix
brew config

Une valeur arm64 ou x86_64 isolée ne suffit pas. Il faut déterminer si le binaire, l’interpréteur, le gestionnaire de paquets et l’artefact restauré parlent la même architecture. Cette méthode est plus fiable que la lecture du seul label du runner.

Élément à comparer Indice d’incompatibilité Correction contrôlable
Binaire natif file révèle une architecture différente de celle du processus Réinstaller ou reconstruire le binaire sur le runner cible
Gem Ruby avec extension C Échec dans bundle install ou chargement d’une extension Supprimer les gems compilées et relancer l’installation
Module Node natif Erreur lors de npm ci, yarn install ou de l’exécution Recréer node_modules et vérifier la version de Node
Paquet Homebrew Préfixe ou bouteille différent Installer explicitement l’outil dans l’environnement choisi
Cache de dépendances Artefact restauré depuis une autre architecture Ajouter l’OS, l’architecture et l’outil à la clé de cache

Vous pouvez conserver un runner Intel si un plug-in, un binaire propriétaire ou une étape de signature n’est pas prêt pour ARM. À l’inverse, ARM est cohérent si vos dépendances sont natives, régulièrement reconstruites et testées sur cette architecture. Le choix ne doit pas être fondé sur une préférence théorique : faites correspondre le runner au format des artefacts que vous distribuez.

Xcode explicite contre sélection implicite

Une erreur Xcode recouvre trois situations différentes. Le runner peut ne pas contenir la version demandée, le système peut sélectionner une autre installation, ou le projet peut réellement ne pas être compatible avec le SDK utilisé. Les messages sont parfois proches, mais les remèdes ne le sont pas.

Ajoutez au début du job une collecte lisible :

xcode-select -p
xcodebuild -version
xcodebuild -showsdks
xcrun simctl list runtimes

Pour sélectionner un outil déjà présent, utilisez le chemin documenté dans l’image :

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

Dans un workflow, DEVELOPER_DIR peut aussi rendre la sélection visible et reproductible. La documentation Apple sur la configuration des outils en ligne de commande explique la relation entre Xcode sélectionné et outils exécutés.

Ne considérez pas macOS 26 comme une promesse que toutes les versions de Xcode, tous les SDK ou tous les runtimes de simulateur seront disponibles. Contrôlez le fichier de l’image macOS 26 au moment de l’exécution et comparez-le aux exigences publiées par Apple pour votre projet.

La correction doit suivre cet ordre :

  1. sélectionner explicitement Xcode ;
  2. afficher Xcode, SDK et destination dans les logs ;
  3. fixer temporairement une image connue ;
  4. tester le projet avec un environnement propre ;
  5. séparer la validation de la mise à niveau de Xcode.

Un build qui passe après une sélection manuelle n’est pas encore une migration terminée. Vous devez aussi vérifier l’archivage, les tests sur simulateur et la signature avec la même sélection.

Préinstallé et mis en cache : deux dépendances cachées

Les runners GitHub-hosted sont pratiques parce qu’ils fournissent de nombreux outils, mais cette commodité crée une dépendance implicite. Une mise à jour de Ruby peut changer le comportement d’un script ; une version de Node peut modifier la compilation d’un module natif ; OpenSSL peut exposer une incompatibilité ; Homebrew peut installer une formule différente de celle attendue.

Le cache ajoute une deuxième couche. Si sa clé ne contient que le nom du projet ou le fichier de verrouillage, elle peut restaurer un artefact provenant d’un autre système, d’une autre architecture ou d’un autre Xcode. La documentation GitHub sur la mise en cache des dépendances recommande de concevoir les clés et les conditions de restauration avec soin.

Une clé plus explicite peut inclure des dimensions comme :

key: ${{ runner.os }}-${{ runner.arch }}-xcode-${{ env.XCODE_VERSION }}-${{ hashFiles('**/Package.resolved') }}

Adaptez cette structure à votre gestionnaire de dépendances ; ne copiez pas une clé qui ignore les composants réellement compilés. Lors d’un incident, désactivez temporairement la restauration du cache ou utilisez une nouvelle clé. Si le build propre passe et le build avec cache échoue, vous disposez d’un indice fort, mais pas encore d’une preuve que l’image est fautive.

Le correctif durable consiste à installer explicitement les versions nécessaires, à conserver les fichiers de verrouillage et à faire échouer le job lorsque la version observée n’est pas celle attendue. L’objectif n’est pas de reproduire éternellement l’état actuel d’une image flottante, mais de rendre votre chaîne responsable de ses outils.

Attention : un cache valide accélère une compilation, mais il ne valide ni l’architecture de l’artefact ni la compatibilité du SDK. Une exécution « verte » après restauration ne doit pas remplacer un passage propre périodique.

Signature, trousseau et simulateur : des pannes à traiter séparément

Un build compilé n’est pas nécessairement un produit installable ou publiable. La signature peut échouer après une compilation réussie, notamment si le certificat, le profil, le trousseau ou les permissions ne sont pas disponibles dans le contexte non interactif du runner.

Pour une panne de signature, conservez les informations non sensibles suivantes :

  • l’identifiant de la cible et de la configuration ;
  • l’équipe de signature attendue ;
  • le profil détecté ;
  • le trousseau utilisé ;
  • la commande exacte d’archivage ;
  • le stade précis où codesign, xcodebuild archive ou l’export échoue.

Ne placez jamais de certificat, de mot de passe ou de contenu de secret dans les logs. Employez des variables masquées et des valeurs génériques comme CERTIFICATE_BASE64 ou KEYCHAIN_PASSWORD. Si l’archivage est réussi mais l’export échoue, traitez l’export comme une panne distincte.

Pour un simulateur absent, listez les runtimes et destinations disponibles avant de modifier le projet :

xcrun simctl list devices available
xcodebuild -showdestinations -scheme "$SCHEME"

Un test sans interface graphique peut également échouer si un script attend une session utilisateur, un accès au trousseau déverrouillé ou une permission accordée manuellement. La correction doit donc préciser le contexte d’exécution, puis être validée sur une tâche non interactive.

Méthode de reprise : cinq étapes avant de migrer

1. Geler les preuves

Conservez le journal du dernier succès et celui de l’échec. Notez le commit, le label demandé, l’image, l’architecture, Xcode, le SDK, la clé de cache et l’étape fautive. Ne supprimez pas l’exécution échouée après une relance réussie.

2. Reproduire sans modifier le projet

Relancez le même commit avec les mêmes paramètres, si le runner et l’image documentés le permettent. Si l’échec disparaît, marquez-le comme intermittent et recherchez une dépendance externe ou un test non déterministe plutôt que de réécrire immédiatement la configuration.

3. Réduire le périmètre

Créez un workflow de diagnostic qui installe ou sélectionne explicitement Xcode, affiche l’architecture, liste les SDK et exécute une installation propre. Retirez temporairement le cache, la signature et les tâches parallèles afin d’identifier la première divergence.

4. Fixer temporairement l’environnement

Remplacez macos-latest par un label documenté et déjà validé pour la branche concernée. Cette décision est un filet de sécurité, pas une garantie éternelle : programmez une validation séparée avant toute mise à jour d’image.

5. Valider la chaîne complète

Un correctif n’est accepté qu’après un build propre, un build avec cache, les tests, l’archive, l’export signé et une nouvelle exécution après redémarrage du nœud. Si une étape n’est pas nécessaire dans votre projet, documentez explicitement son exclusion au lieu de la considérer comme implicitement validée.

La checklist de décision pour le runner

Utilisez cette liste pendant la revue d’incident :

  • [ ] Le journal de Set up job a été comparé à la dernière exécution réussie.
  • [ ] L’image réellement utilisée a été relevée dans le journal et rapprochée de la liste officielle.
  • [ ] L’architecture du système et celle des binaires natifs ont été vérifiées.
  • [ ] Xcode, le répertoire développeur, le SDK et les runtimes de simulateur sont enregistrés.
  • [ ] Le cache a été testé avec une clé intégrant l’OS, l’architecture et l’outil concerné.
  • [ ] Une installation propre des dépendances passe sans restaurer l’ancien cache.
  • [ ] La compilation, les tests, l’archive et l’export signé ont été exécutés séparément.
  • [ ] Les secrets de signature ne sont pas apparus dans les journaux.
  • [ ] Le workflow a été rejoué après redémarrage du runner ou du nœud.
  • [ ] Le choix entre tag fixe et Mac distant est documenté avec un responsable de maintenance.

Si les quatre premières cases échouent, ne migrez pas encore : vous manquez de preuves. Si les outils sont maîtrisés mais que le réseau privé, le cache durable ou un processus continu devient indispensable, comparez plutôt un runner auto-hébergé et un Mac distant.

Tag fixe contre Mac distant auto-hébergé

Un tag fixe convient à une panne immédiate lorsque le job est court, sans état persistant et compatible avec les limites d’un environnement éphémère. Il permet de restaurer rapidement une combinaison connue, tout en laissant à l’équipe le temps de préparer une mise à niveau.

Un Mac distant auto-hébergé devient plus intéressant lorsque la chaîne doit conserver un environnement Xcode stable, accéder à des services internes, garder un cache volumineux ou exécuter un processus en continu. Il apporte davantage de contrôle, mais vous devez alors gérer les mises à jour, les accès SSH, les secrets, le nettoyage du trousseau et la disponibilité du nœud. La référence GitHub sur les runners hébergés aide à distinguer ce qui est fourni par l’environnement hébergé de ce qui relève de votre administration.

Pour une équipe qui hésite, la bonne séquence n’est pas de remplacer immédiatement tous les jobs. Il vaut mieux cloner le workflow sur un nœud isolé, sélectionner Xcode explicitement, reconstruire le cache, tester la signature et provoquer un redémarrage contrôlé. Cette répétition révèle les dépendances invisibles avant qu’elles ne touchent la production.

Si votre configuration s’oriente vers un runner plus puissant ou spécialisé, consultez aussi la documentation des larger runners, puis comparez-la à une machine dont vous contrôlez réellement l’état. Le critère principal n’est pas le nom du service : c’est la capacité à expliquer et à reproduire chaque étape du pipeline.

Lorsque votre solution actuelle repose uniquement sur macos-latest, elle vous expose à un mappage qui peut évoluer, à des outils préinstallés que vous ne maîtrisez pas entièrement et à des caches difficiles à interpréter. Elle devient également moins adaptée dès qu’un accès privé, une session persistante ou une chaîne de signature durable est nécessaire. Dans ce cas, louer un Mac auprès de VMSPIN peut offrir un environnement distant plus stable pour une phase de validation : vous pouvez y rejouer le workflow réel, vérifier la persistance après redémarrage et décider ensuite, preuves à l’appui, si la production doit migrer. Les options disponibles sont présentées sur la page des formules de location Mac.

Questions fréquentes sur macos-latest et les runners Mac

Comment vérifier le mappage actuel de macos-latest ?

Ne vous fiez pas au nom du label affiché dans le fichier YAML. Ouvrez Set up job, relevez l’image, le système, l’architecture et les versions d’outils, puis rapprochez-les de l’annonce GitHub et du dépôt officiel des images. Le mappage doit être contrôlé à chaque incident lié à l’environnement, surtout après une annonce de migration.

Pourquoi le même workflow peut-il fonctionner puis échouer sans nouveau commit ?

Le workflow dépend non seulement du dépôt, mais aussi de l’image, de Xcode, du SDK, des paquets préinstallés, des simulateurs et du cache restauré. Une différence dans l’un de ces éléments peut expliquer l’échec, mais elle doit être démontrée dans les journaux. Si aucune différence n’apparaît, revenez à la comparaison du commit et des paramètres d’exécution.

Quelle méthode appliquer pour choisir ARM ou Intel ?

Commencez par uname -m, puis inspectez les exécutables et extensions natives avec file. Vérifiez enfin le préfixe de Homebrew et l’origine du cache. ARM est approprié lorsque vos dépendances sont reconstruites et validées dans cet environnement ; Intel reste préférable lorsqu’un composant critique ne fournit pas encore un binaire compatible. Le label seul ne constitue pas une preuve.

Comment éviter qu’un cache casse après un changement d’image ?

Incluez dans la clé les dimensions qui modifient réellement l’artefact : système, architecture, Xcode, SDK ou fichier de verrouillage. Lors d’un incident, testez un build propre avec une clé neuve. Si le résultat diffère, détruisez ou isolez l’ancien cache, puis documentez la règle de renouvellement. Un cache accélère le job, mais ne doit jamais définir implicitement votre outil de compilation.

Quand tester un Mac distant avant de changer la production ?

Faites ce test lorsque vous avez besoin d’un Xcode stable, d’un cache persistant, d’un accès à un réseau privé, d’un processus continu ou d’une chaîne de signature contrôlée. Rejouez le commit réel sur un nœud isolé, puis vérifiez build propre, cache, tests, archive, export et reprise après redémarrage. La migration ne doit suivre qu’après cette validation complète.

Un runner flottant est acceptable pour un job sans état lorsque vous surveillez régulièrement ses changements. Il devient risqué dès que l’environnement est une partie non documentée du produit livré. En pratique, conservez le tag fixe comme mesure de récupération, puis utilisez une instance Mac distante de VMSPIN pour éprouver une chaîne contrôlée avant de décider d’un changement durable. Vous pouvez commencer par examiner les solutions Mac disponibles pour votre environnement sans remplacer votre pipeline actuel.