Jeudi 29 mai 2026, 15 h 03. Dans une équipe iOS de douze personnes, quelqu'un poste dans Slack #ios-ci le lien d'un run avec ce message : « En local, Xcode passe en douze minutes — le PR est encore en Queued. » Silence pendant une demi-heure : ce genre de ping, c'est le quotidien. Quarante-huit minutes plus tard, la même personne ajoute : « Queued 47m, run 12m. Pas de merge ce soir. » Un collègue du track Release sort d'une réunion, jette un œil à son téléphone : « L'Archive ios-release est toujours en file — la ligne PR est encore bloquée. »
Les captures proviennent de documents clients partagés lors du dépannage — anonymisés, chiffres intacts. Vous pouvez reproduire les stats via la doc GitHub sur le temps d'exécution des jobs et queue_wait_seconds.
Deux apps dans un monorepo, CI entièrement sur des runners macOS hébergés par GitHub. En 2025, ça passait ; au printemps 2026, la boucle de feedback s'allonge jusqu'à devenir insupportable. GitHub Actions n'est pas « cassé », mais pour les équipes iOS, ça ressemble de plus en plus à un bus partagé saturé aux heures de pointe.
Ce jeudi-là : ouvrir la page Job — la barre jaune quatre fois plus longue que la verte
Ils nous ont envoyé une capture de la page Job du run #18472910356 — pas du bruit de couloir, une timeline avec des chiffres. Jaune (Queued) : 47 min 12 s, vert (Run) : 12 min 4 s, en bas de l'interface : queue_wait_seconds: 2832, run duration: 724. La formule est limpide : wait time >> run time.
ios-pr.yml · macos-latest · 2026-05-29 15:04 CSTEn réunion, l'équipe débattait encore du parallélisme -jobs ou d'une nouvelle clé de cache DerivedData — sans avoir extrait les colonnes queued et run des dix derniers jobs macOS. Côté Release, c'était pire : ios-release.yml empilait xcodebuild archive, notarisation, upload TestFlight et tests unitaires PR sur la même ligne, le même label macos-latest. Un archive prend 25 à 40 minutes, monopolise des slots de concurrence rares et entraîne les jobs PR rapides dans la file.
Si vous voulez d'abord traiter le Queued, commencez par notre runbook de diagnostic file macOS runner. Le tournant est venu quand l'équipe a admis que le problème était la capacité — et a convaincu la salle avec des chiffres, pas des impressions.
Preuves : tableau recopié à la main de dix jobs failed
Avant le stand-up du 30 mai, quelqu'un a recopié dix jobs macOS failed depuis la liste Actions (voir fig. 3). Pas de BI — juste un tableau, deux colonnes clés : queued_s et run_s. Médiane queued 2650 s, run 724 s ; ratio jusqu'à 4,5×. Quelques secondes de silence en salle : plus personne n'accusait Xcode 17.4.
| Souvent pris pour | Signal dans fig. 3 | Cause plus probable |
|---|---|---|
| Panne GitHub | 10/10 queued_s > run_s | Plafond de concurrence macOS, jobs lourds qui bloquent les slots |
| Projet qui grossit | run_s stable entre 690–811s | La compilation n'est pas le goulot — l'attente, si |
| Pushes anarchiques | Plusieurs runs par PR (voir fig. 2) | Boucle Agent / bot |
| Certificat expiré | Échecs de signature répartis sur plusieurs runs | Keychain à froid, contention parallèle |
fig. 2 : le même PR déclenché six fois
La file n'était que la première mi-temps. En mars, l'équipe avait branché un agent de code : CI rouge → lire les logs → commit auto → relancer Actions. La page Checks du PR #847 est explicite — six workflow runs, dont quatre par un bot, deux par des humains. Officiellement de la CI ; en pratique, une boucle de retry. Voir Ce qui se passe quand la CI devient une boucle.
ios-pr.yml (4× bot · 2× humain) · 2026-05-29Plus tard, quelqu'un plaisanta : « La CI n'est pas morte — on l'a transformée en mouvement perpétuel. » Ce n'est pas l'automatisation qui pose problème, c'est que chaque fix bot grignote la file partagée sans pool de runners dédié.
Tournant : un Cloud Mac et la séparation des pipelines le 31 mai
Après le tableau, l'équipe a découpé les logs par type de job : environ 70 % de vérifs PR et builds d'intégration, 20 % de tests unitaires simulateur, 10 % d'archive et upload — proche de la composition mesurée d'une autre équipe à 500 CI iOS par jour. Idées : huit Mac mini ou migrer vers Xcode Cloud. Deux décisions ont été prises :
Le 31 mai : louer un Cloud Mac Apple Silicon, l'enregistrer en runner auto-hébergé (labels self-hosted, macOS, cloud-mac). L'expérience contrôle était simple : même commit, 50 builds sur macos-latest hébergé et sur le Cloud Mac — la variance queued passa de « parfois 40 minutes, parfois 5 » à « quasi toujours quelques secondes ».
Séparation des pipelines la même nuit. Archive, export et upload sortis de ios-pr.yml vers ios-release.yml ; la ligne PR ne garde que build et tests unitaires. Ajout d'un cron de warm-up : chaque nuit, xcodebuild build sur le Cloud Mac, DerivedData sur disque — plus fiable qu'un tirage froid depuis actions/cache à chaque run.
fig. 6 · Médiane du wall time de la même équipe (build PR, 50 runs sur deux semaines chacun)
Source : journaux d'expérience contrôle client (2026-05-31 — 06-08), recoupables avec fig. 1 et fig. 4.
9 juin : fig. 4 — enfin une page Job qui a du sens
Deux semaines après le split, lundi matin : dans Slack, capture du run #18510488201 — queued 41 s, run 9 min 17 s, runner self-hosted, cloud-mac. Pas de likes, juste une réaction — chez les équipes iOS, c'est le plus haut compliment.
self-hosted, cloud-mac · 2026-06-09 09:11 CSTGarde-fous pour l'Agent : quatre règles dans le Wiki
Le split règle le blocage Archive sur les PR ; la boucle bot demande autre chose. Branche dédiée et pool de runners basse priorité pour le bot — merge après review humaine. Quatre règles figées dans le Wiki :
- Workflow PR : pas d'archive / pas d'upload store
- Jobs macOS avec
concurrencypour éviter les runs empilés sur la même branche - Runners auto-hébergés étiquetés — pas de mélange avec des workflows expérimentaux
- Push auto de l'Agent sur branche séparée, pas de push direct sur branches protégées
queue_wait_seconds et run duration (comme fig. 3) ; comptez les runs de workflow par PR (comme fig. 2) ; vérifiez si l'archive est encore dans le workflow PR. Si la médiane queued > run, parlez capacité runner avant les flags de compilation.
La nouvelle approche : garder GitHub, changer la capacité
L'expérience de cette équipe se résume en quatre actions que les équipes iOS déploient le plus en 2026 : pool de runners Cloud Mac auto-hébergés, split PR / Release, warm-up et cache disque, garde-fous Agent. Xcode Cloud remplace-t-il tout ? Adapté à l'écosystème Apple pur ; avec checks PR GitHub et pipelines Android mixtes, la plupart gardent Actions et déplacent seulement l'archive vers du macOS dédié.
| Si votre équipe ressemble à… | Recommandation courante |
|---|---|
| 1–3 personnes, release hebdo | macOS hébergé + cache agressif ; archive Release manuel |
| 5–15 personnes, merge quotidien | 1–2 Cloud Mac auto-hébergés + split PR/Release |
| 15+ personnes, plusieurs apps | Pool de runners par app (labels) + cron warm-up |
| Fixes Agent intensifs | Pool bot dédié + pas d'archive dans le workflow PR |
La doc Apple xcodebuild insiste sur l'alignement scheme / destination ; si la CI repart à froid à chaque fois, « ça passe en local » perd son sens. Comme disait un client : mieux vaut rotation de certificats et gros upgrade Xcode que regarder une barre jaune de 47 minutes chaque jour.
En commençant par un premier runner Cloud Mac auto-hébergé
Si votre page Job ressemble à fig. 1 — jaune plus long que vert — le prochain investissement le plus rentable est souvent un Cloud Mac Apple Silicon dédié : enregistré comme runner GitHub auto-hébergé, le build PR passe de « loterie de file » aux neuf minutes prévisibles de fig. 4.
Deux semaines d'expérience contrôle : même branche, 50 builds sur macOS hébergé et Cloud Mac auto-hébergé — un tableau comme fig. 3. Les chiffres diront si c'est Xcode qui traîne ou la capacité qui freine le dev iOS.
Partez d'un Cloud Mac prévisible et remettez l'archive sur la ligne Release. Voir les offres Cloud Mac VPSSpark et enregistrer votre premier runner iOS auto-hébergé.