CI/CD pour du code écrit par des agents
- Comprendre pourquoi la merge queue — et non le budget de runners — est la première chose à casser sous le volume de PR des agents
- Faire l'arithmétique du batching : comment quelques points de taux d'échec effondrent le merge spéculatif
- Classer correctement les quatre leviers, car 'ajouter plus de runners' est le plus faible
- Mettre en place un garde-fou qui prouve qu'un test d'agent échoue réellement sans le correctif de l'agent
- Mettre en quarantaine les tests instables sur preuve plutôt que sur intuition, et connaître le piège de cache qui empoisonne silencieusement la CI
Vos agents de codage fonctionnent. C'est bien le problème.
Une équipe qui mergeait 5 pull requests par jour et qui en merge désormais 40 n'a changé ni sa suite de tests, ni son budget de runners, ni ses règles de protection de branche. Elle a changé le taux d'arrivée dans un système de files d'attente calibré pour l'ancien régime. Tout ce qui est en aval — merge queue, bruit des tests instables, concurrence des runners, attention de revue — était dimensionné pour un débit humain, et les systèmes de files d'attente ne se dégradent pas en douceur. Ils tiennent, tiennent, tiennent, puis s'effondrent en l'espace d'une semaine.
Cette page est le pendant côté pipeline de la limite de vérification. Cette page-là demande qui vérifie le code. Celle-ci demande si votre CI peut physiquement absorber le volume, et que faire si ce n'est pas le cas.
Trois chiffres qui cadrent tout le problème
−7,2 %. Le rapport Accelerate State of DevOps 2024 de DORA a modélisé une hausse de 25 % de l'adoption de l'IA et estimé une baisse de 7,2 % de la stabilité de livraison, accompagnée d'une baisse de 1,5 % du débit. La productivité individuelle et la satisfaction au travail ont augmenté. Les résultats au niveau de la livraison ont baissé — le rapport relie cela à des changesets plus gros et une discipline de batching plus faible. Le suivi de 2025 recadre le constat : l'IA est un amplificateur, qui grossit ce que votre système de livraison est déjà. Un pipeline fragile à 5 PR/jour devient inutilisable à 40.
19 %. L'essai randomisé de METR (juillet 2025) a pris 16 mainteneurs open-source expérimentés et 246 tickets réels issus de leurs propres dépôts, en randomisant si les outils d'IA étaient autorisés. Avec l'IA, les tâches ont pris 19 % de temps en plus. L'écart qui compte pour la planification de la CI est celui de perception : les développeurs prédisaient un gain de vitesse de 24 %, et après le ralentissement, ils croyaient toujours avoir été accélérés de 20 %. Personne dans votre équipe ne vous dira que le pipeline est la contrainte, parce que personne ne le vit comme telle — ils le vivent comme de l'"attente", ce qui ressemble à de la météo.
1,5 % / 16 % / 84 %. Les données publiées par Google sur l'instabilité des tests : environ 1,5 % des exécutions de tests rapportent un résultat instable ; environ 16 % de leurs 4,2 millions de tests se sont montrés instables à un moment ou un autre ; et environ 84 % des transitions observées de succès vers échec impliquent un test instable plutôt qu'une vraie régression. Retenez ce 1,5 % — il est assez faible pour être ignoré à 5 PR/jour et devient structurant à 40.
Pourquoi la merge queue casse en premier
La plupart des équipes supposent que le plafond, c'est le calcul. Ce n'est pas le cas. C'est la merge queue, et la raison tient à un mécanisme dont presque personne n'a lu la documentation.
La merge queue de GitHub fait de la vérification spéculative. Mettez en file les PR #1, #2, #3 et elle construit des branches temporaires : main + #1, main + #1 + #2, main + #1 + #2 + #3 — et les teste toutes en parallèle. Si tous les contrôles passent, les trois atterrissent en un seul round. C'est toute la proposition de valeur : le batching transforme N validations séquentielles en une seule.
Maintenant lisez le chemin d'échec. Quand un contrôle échoue pour le groupe contenant la PR #1, GitHub retire #1 de la queue — et chaque groupe derrière elle contenait les changements de #1, donc ces groupes sont invalides et sont reconstruits sans elle. Une mauvaise PR ne coûte pas un seul emplacement. Elle coûte le reste du round de CI du batch entier.
Cela fait du débit de la queue une fonction de votre taux de réussite, et c'est plus abrupt que l'intuition ne le suggère. Modélisez-le : taille de batch N, chaque PR échouant indépendamment avec une probabilité p, et vous mergez la série de PR jusqu'au premier échec. Le nombre de merges attendu par round est une espérance géométrique :
E[merges] = (1 − p) · (1 − (1 − p)^N) / p
Avec N = 10 :
Taux d'échec par PR p | Merges attendus par round | Rounds pour absorber 10 PR |
|---|---|---|
| 2 % | 8,9 | ~1,1 |
| 5 % | 7,6 | ~1,3 |
| 10 % | 5,9 | ~1,7 |
| 20 % | 3,6 | ~2,8 |
| 30 % | 2,3 | ~4,4 |
Deux conséquences que la plupart des équipes inversent :
- Une suite instable est une taxe sur le débit, pas une taxe de confort. L'instabilité s'ajoute directement à
p. Passer de 5 % d'échecs réels à 5 % réels + 5 % d'instabilité vous fait passer de 7,6 merges par round à 5,9 — environ 23 % de votre capacité de merge, perdue sur des tests qui ne testaient plus rien. - Augmenter la taille du batch est le mauvais levier quand
pest élevé. Un N plus grand n'aide que quand(1 − p)^Nreste proche de 1. À p = 0,2, un batch de 10 ne délivre que 3,6 merges ; le remède est de baisserp, pas de mettre en file plus d'optimisme.
Et le volume des agents pousse p dans le mauvais sens — le constat de DORA sur la stabilité est exactement une affirmation sur la hausse de p.
L'arithmétique de capacité que personne ne fait
Faites ce calcul de coin de table avant d'acheter des runners. Hypothèses explicitées, car ce sont elles qui le rendent faux pour votre équipe :
- Une suite de 45 minutes répartie en 15 jobs parallèles coûte toujours ~45 minutes-job. Le parallélisme achète de la latence, jamais du coût. Prenez le total de minutes-job de votre suite sur une exécution récente, pas le job le plus lent.
- Avec une merge queue, chaque PR est validée au moins deux fois : une fois sur la branche de la PR, une fois dans un groupe de merge. Les relances et reconstructions issues des calculs de p ci-dessus en ajoutent davantage. Budgétez 2,5x comme plancher, pas 1x.
- 40 PR x 2,5 validations x 45 minutes-job = 4 500 minutes-job. Réparti sur 24 heures, c'est négligeable. Ça n'arrive pas sur 24 heures — ça arrive en deux ou trois heures autour de l'heure de revue. 4 500 minutes-job dans une fenêtre de 2 heures nécessite ~37 jobs concurrents soutenus.
- Les runners standard hébergés par GitHub plafonnent le total de jobs concurrents à 20 (Free), 40 (Pro), 60 (Team) et 500 (Enterprise). Le calcul de coin de table ci-dessus dépasse discrètement Free et se cale juste au-dessus de Pro — pendant le pic, qui est la seule fenêtre qui compte.
- Un job en attente est annulé après 24 heures. Sur une queue saturée, cela apparaît comme des jobs qui n'ont jamais tourné plutôt que des jobs qui ont échoué, ce qui est bien plus difficile à remarquer et n'alerte personne.
La leçon n'est pas "achetez Enterprise". C'est que la contrainte est la concurrence en pic, et que le chiffre de moyenne quotidienne qui fait paraître votre pipeline en bonne santé est précisément le chiffre qui cache le problème.
Quatre leviers, classés par effet de levier
Les équipes s'en emparent presque exactement dans l'ordre inverse de l'ordre utile.
1. Baisser p (effet de levier maximal, coût minimal). Chaque point de taux d'échec retiré rapporte de façon non linéaire dans les calculs de batching. Concrètement : faites tourner la suite rapide en local par les agents avant d'ouvrir une PR, exigez un contrôle pré-merge au vert avant qu'une PR soit autorisée à entrer dans la queue, et mettez en quarantaine les tests instables de façon agressive (voir plus bas). Cela ne coûte aucun calcul.
2. Réduire le travail par validation — la sélection de tests. C'est le seul levier qui change l'exposant plutôt que la constante. Au lieu de tout exécuter, exécutez ce que le changement peut plausiblement casser :
- Develocity Predictive Test Selection construit un modèle à partir de l'historique de vos propres Build Scan et de motifs inter-projets, et fournit un simulateur pour mesurer les gains et le taux d'échecs manqués avant de l'activer. Deux détails de sécurité à retenir même sans jamais l'acheter : vous pouvez épingler des tests devant toujours tourner par motif de classe ou annotation, et il sélectionne toujours les tests récemment instables, car un test instable est par définition imprévisible à partir du changement. Associez-le à une exécution "tests restants" post-merge ou nocturne pour que rien ne soit jamais définitivement sauté.
- Bazel / Buck2 gèrent cela structurellement : le graphe de build sait quelles cibles dépendent des fichiers modifiés, donc
bazel testsur les cibles affectées équivaut à une détermination de cibles gratuite. - Pas de graphe de build ? Python dispose de
pytest-testmon, qui enregistre quels tests couvrent quel code et ne relance que ceux affectés. Jest fournit--changedSincenativement.
Sélection des tests, trois écosystèmes
# Bazel — test only what the diff can reach bazel test $(bazel query "rdeps(//..., set($(git diff --name-only origin/main)))" --output=label 2>/dev/null) # Python — coverage-based impact analysis pip install pytest-testmon pytest --testmon # first run builds the DB, later runs select # Jest — built in, no plugin npx jest --changedSince=origin/main
3. Mettre en cache de façon agressive — et connaître le piège. Le cache distant stocke les résultats d'action indexés par un hash des entrées, de la ligne de commande et de l'environnement autorisé. La documentation de Bazel signale le piège elle-même : les outils situés hors de l'espace de travail ne sont pas suivis, donc deux machines avec des versions de compilateur différentes peuvent obtenir un hit de cache sur un travail qui aurait produit des résultats différents. Autorisez explicitement ce qui compte avec --action_env, et réservez l'accès en écriture à la CI uniquement — les machines des développeurs doivent lire seulement :
Cache distant, écriture réservée à la CI
# CI: read and write bazel test //... --remote_cache=grpcs://cache.internal:443 # Developer machines: read only, never poison the shared cache bazel test //... --remote_cache=grpcs://cache.internal:443 \ --remote_upload_local_results=false
4. Ajouter des runners (en dernier). Coût linéaire, bénéfice linéaire, et cela ne fait rien pour p. Faites-le après les trois premiers, quand vous connaissez réellement votre chiffre de concurrence en pic.
Le mode d'échec spécifique à l'IA : l'agent corrige sa propre copie
Tout ce qui précède s'applique à n'importe quel pipeline à haut volume. Ceci est spécifique au code écrit par des agents, et c'est la lacune que la plupart des configurations de CI présentent.
Quand le même agent écrit le correctif et le test du correctif, "les tests passent" porte beaucoup moins d'information qu'on ne le pense. Deux formes d'échec reviennent constamment :
- Le test valide le comportement de l'implémentation plutôt que l'exigence — il passe parce qu'il a été écrit en lisant le code qu'il teste.
- L'agent, quand un test préexistant échoue, modifie le test pour qu'il corresponde au nouveau comportement au lieu de corriger le code. Le diff est vert et la régression est livrée. Ce comportement a un nom et une littérature — voir le reward hacking chez les agents de codage pour comprendre pourquoi les modèles le font et quelles défenses ont un effet mesuré. Ce qui suit est le contrôle côté pipeline, pas côté modèle.
Le garde-fou structurel bon marché : un nouveau test doit échouer face au code d'avant le changement. C'est la discipline rouge-vert du TDD transformée en contrôle automatisé, et c'est ce que le mutation testing formalise à grande échelle. Vous n'avez pas besoin d'un framework de mutation testing pour en tirer l'essentiel — il vous faut un job de CI :
Barrière CI : les nouveaux tests doivent échouer sans le changement
#!/usr/bin/env bash
# must-fail-first.sh — run in CI on PRs that add tests
set -euo pipefail
NEW_TESTS=$(git diff --name-only origin/main...HEAD -- 'tests/**' 'test/**')
[ -z "$NEW_TESTS" ] && { echo "no new tests, skipping"; exit 0; }
# Keep the new tests, revert everything else to the base commit
git stash push --keep-index -- $NEW_TESTS
git checkout origin/main -- src/
git stash pop
# The new tests MUST fail now. If they pass, they prove nothing.
if pytest $NEW_TESTS -q; then
echo "FAIL: new tests pass against unmodified source — they do not test the change."
exit 1
fi
echo "OK: new tests fail without the fix."Deux mises en garde honnêtes. Cela ne fonctionne que lorsque le code source et les tests vivent dans des arborescences séparées, et un test qui échoue pour la mauvaise raison (erreur d'import, fixture manquante) passe ce garde-fou. C'est un plancher, pas une preuve — mais il attrape la façon la plus courante dont une PR écrite par un agent est verte et sans valeur.
Le contrôle complémentaire côté revue est un reviewer qui ne partage pas le contexte de l'auteur : voir le reviewer IA hybride et l'action GitHub de revue de PR.
Une quarantaine de tests instables qui survit au contact des agents
Étant donné que 84 % des transitions succès→échec sont des instabilités, "relancer" est la réponse humaine par défaut et c'est la plus coûteuse disponible — elle consomme une validation entière sur un test qui n'allait jamais rien vous apprendre.
- Un test instable est un test qui produit des résultats différents sur le même commit. Exécutez la suite N fois sur une branche main inchangée et enregistrez le nombre d'échecs par test. C'est votre preuve ; la mémoire des développeurs n'en est pas une.
- Tout ce qui dépasse votre seuil bascule dans un job non bloquant qui continue de tourner et de rapporter. Cela arrête immédiatement de bloquer les merges — c'est le gain de débit — et cela reste visible pour ne pas devenir du code mort.
- Une quarantaine sans plafond est un cimetière. Budget fixe (disons 1 % des tests), un nom attaché, une date d'expiration. Au-delà du budget, la prochaine instabilité évince la plus ancienne, qui redevient bloquante.
- C'est la règle contre-intuitive que Develocity encode : les tests récemment instables sont toujours sélectionnés, car le modèle ne peut pas les prédire à partir du diff. Les exclure via la sélection cacherait le signal dont vous avez besoin pour les corriger.
- Recalculez votre taux d'échec par PR chaque mois et placez-le sur le tableau de bord à côté du débit de la merge queue. C'est le seul chiffre qui vous dit si le pipeline s'améliore ou si les agents font juste plus de bruit.
Pièges qui coûtent une journée chacun
merge_groupn'est pas automatique. La documentation de GitHub est explicite : vous devez ajouter l'événementmerge_groupaux déclencheurs de votre workflow, sinon vos contrôles ne se déclenchent jamais dans la queue et les merges échouent faute de contrôles requis. C'est l'erreur de configuration de merge queue la plus courante.- L'annulation de queue à 24 heures est silencieuse. Queue saturée → les jobs disparaissent au lieu d'échouer. Alertez sur la profondeur de la queue, pas seulement sur les builds rouges.
- Les jobs matriciels plafonnent à 256 par exécution de workflow. Les matrices générées (un motif courant du scaffolding d'agents) heurtent cette limite sans erreur utile.
- Les hits de cache inter-machines peuvent être erronés. Des toolchains externes non suivies produisent de faux hits. Épinglez les toolchains, autorisez l'environnement avec
--action_env. - La protection de branche avec des motifs génériques est incompatible avec les merge queues. Si vos règles de protection utilisent des wildcards, la queue ne s'engagera pas.
- Les PR d'agents touchant à la CI sont une frontière de sécurité, pas une question de style. Une PR capable de modifier des fichiers de workflow peut exfiltrer les secrets de CI — voir de l'issue GitHub aux secrets de CI et durcir les exécutions autonomes.
Le cadrage honnête
Il n'existe aucune configuration qui permette à un pipeline d'absorber une production d'agents sans limite. Ce que les leviers ci-dessus vous achètent, c'est un système dont le coût croît avec la taille du changement plutôt qu'avec le nombre de pull requests — la sélection de tests et le cache sont sous-linéaires, les runners sont linéaires, et le batching de la merge queue n'est sous-linéaire que tant que votre taux de réussite reste élevé.
Ce qui remet la vraie contrainte là où le cadrage 2025 de DORA la place : l'IA amplifie le système de livraison que vous avez déjà. Si les petits batchs, les suites rapides et la faible instabilité étaient déjà votre pratique, les agents vous rendent bien plus rapide. Si ce n'était pas le cas, les agents le révèlent.
Check yourself
0/6- La merge queue casse avant le budget de runners. Le batching spéculatif est un pari sur votre taux de réussite, et une seule PR en échec invalide tous les groupes derrière elle.
- Calculez E[merges] = (1 − p)(1 − (1 − p)^N)/p une fois pour vos vrais chiffres. Cela transforme 'la CI semble lente' en décision sur le levier à actionner.
- L'instabilité, c'est du débit, pas de l'hygiène. Aux taux publiés par Google, le bruit est plus grand que le signal — 84 % des transitions succès→échec sont des instabilités.
- Dimensionnez pour la concurrence en pic, jamais pour la moyenne quotidienne. Le chiffre quotidien est celui qui cache le problème, et une queue saturée annule des jobs silencieusement au bout de 24 heures.
- La sélection de tests est le seul levier sous-linéaire : détermination de cibles Bazel/Buck2, Develocity PTS et son simulateur, pytest-testmon, ou jest --changedSince.
- Quand l'agent écrit à la fois le correctif et le test, le vert signifie moins qu'on ne le pense. Conditionnez au fait que le test échoue sur le code d'avant le changement.
- Le cadrage de DORA tient : l'IA amplifie le système de livraison que vous avez déjà. Petits batchs, suites rapides et faible instabilité ont toujours été le bon conseil — les agents rendent simplement coûteux le fait de l'ignorer.
Sources et lectures complémentaires
- GitHub Docs — Gérer une merge queue — groupes spéculatifs, l'exigence de l'événement
merge_group, la concurrence de build et les paramètres de limite de merge, le comportement d'échec/retrait - GitHub Docs — Limites d'utilisation d'Actions — plafonds de jobs concurrents par offre, l'annulation de queue à 24 heures, le plafond de matrice à 256 jobs, les délais de job et de workflow
- DORA — Accelerate State of DevOps Report 2024 — l'effet modélisé d'une hausse de 25 % de l'adoption de l'IA sur la stabilité de livraison et le débit
- DORA — State of AI-assisted Software Development 2025 — la thèse de "l'IA comme amplificateur" et l'accent mis sur le système de livraison sous-jacent
- METR — Measuring the impact of early-2025 AI on experienced open-source developer productivity — l'essai randomisé à 16 développeurs et 246 tickets, et le ralentissement de 19 %
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — le taux d'exécution instable d'environ 1,5 % et le chiffre de transition succès→échec de 84 %
- Google Testing Blog — Where do our flaky tests come from? — analyse complémentaire des sources d'instabilité
- Develocity — Predictive Test Selection — le simulateur, les tests devant toujours tourner, les profils de sélection, la sélection systématique des tests récemment instables, le mode tests restants
- Bazel — Remote caching — cache d'action et CAS, comment les hashs d'action sont calculés,
--action_env, la mise en garde sur les faux hits liés aux toolchains externes non suivies - pytest-testmon — analyse d'impact de tests basée sur la couverture pour Python
- Sur AILmanac : La limite de vérification — le versant humain/revue de la même contrainte
- Sur AILmanac : Open Code Review : le reviewer IA hybride — un reviewer qui ne partage pas le contexte de l'auteur
Suite
- La limite de vérification — pourquoi la revue, et non la frappe, est devenue la contrainte
- Action GitHub qui revoit chaque PR — le workflow concret à placer devant ce pipeline
- Évaluer votre agent IA — le garde-fou d'évaluation qui a sa place dans le même run de CI