CI/CD per codice scritto dagli agenti
- Capire perché la merge queue — non il budget dei runner — è la prima cosa che si rompe sotto il volume di PR degli agenti
- Fare l'aritmetica del batching: come pochi punti di tasso di fallimento fanno collassare il merge speculativo
- Ordinare correttamente le quattro leve, perché 'aggiungi più runner' è la più debole
- Mettere in produzione un controllo che dimostri che il test di un agente fallisce davvero senza la sua correzione
- Mettere in quarantena i test instabili sulla base di prove e non di sensazioni, e conoscere la trappola della cache che avvelena la CI in silenzio
I tuoi agenti di coding funzionano. È proprio quello il problema.
Un team che faceva merge di 5 pull request al giorno e ora ne fa 40 non ha cambiato la sua test suite, né il budget dei runner, né le regole di branch protection. Ha cambiato il tasso di arrivo dentro un sistema a code tarato su quello vecchio. Tutto ciò che sta a valle — merge queue, rumore dei test instabili, concorrenza dei runner, attenzione in review — era dimensionato per un throughput umano, e i sistemi a code non degradano con grazia. Reggono, reggono, reggono, poi crollano nel giro di una settimana.
Questa pagina è il lato pipeline del collo di bottiglia della verifica. Quella pagina chiede chi controlla il codice. Questa chiede se la tua CI riesce fisicamente ad assorbire il volume, e cosa fare quando non ci riesce.
Tre numeri che inquadrano tutto il problema
−7,2%. Il report DORA 2024 Accelerate State of DevOps ha modellato un aumento del 25% nell'adozione dell'AI e ha stimato una diminuzione del 7,2% nella stabilità della delivery insieme a un calo dell'1,5% nel throughput. Produttività individuale e soddisfazione sul lavoro sono salite. I risultati a livello di delivery sono scesi — il report lo collega a changeset più grandi e a una disciplina di batching più debole. Il follow-up del 2025 lo riformula: l'AI è un amplificatore, che ingrandisce qualunque cosa sia già il tuo sistema di delivery. Una pipeline fragile a 5 PR/giorno diventa inutilizzabile a 40.
19%. Lo studio randomizzato di METR (luglio 2025) ha preso 16 manutentori open-source esperti e 246 issue reali dai loro stessi repository, randomizzando se fosse consentito usare strumenti di AI. Con l'AI, i task hanno richiesto il 19% di tempo in più. Il divario che conta per la pianificazione della CI è quello percettivo: gli sviluppatori prevedevano un'accelerazione del 24% e, dopo il rallentamento, credevano ancora di essere stati accelerati del 20%. Nessuno nel tuo team ti dirà che la pipeline è il vincolo, perché nessuno la vive come tale — la vivono come "attesa", che sembra meteo.
1,5% / 16% / 84%. I dati sulla flakiness pubblicati da Google: circa l'1,5% delle esecuzioni di test riporta un risultato instabile; circa il 16% dei loro 4,2 milioni di test ha mostrato instabilità prima o poi; e circa l'84% delle transizioni pass→fail osservate coinvolge un test instabile e non una regressione reale. Tieni a mente quell'1,5% — è abbastanza piccolo da ignorare a 5 PR/giorno e portante a 40.
Perché la merge queue si rompe per prima
Quasi tutti danno per scontato che il tetto sia la potenza di calcolo. Non lo è. È la merge queue, e la ragione sta in un meccanismo di cui quasi nessuno ha mai letto la documentazione.
La merge queue di GitHub fa controllo speculativo. Metti in coda le PR #1, #2, #3 e vengono costruiti branch temporanei: main + #1, main + #1 + #2, main + #1 + #2 + #3 — e li testa tutti in parallelo. Se tutti i controlli passano, i tre atterrano in un solo giro. È tutta qui la proposta di valore: il batching trasforma N validazioni seriali in una.
Ora leggi il percorso di fallimento. Quando un controllo fallisce per il gruppo che contiene la PR #1, GitHub rimuove #1 dalla coda — e ogni gruppo dietro di essa conteneva le modifiche di #1, quindi quei gruppi non sono più validi e vengono ricostruiti senza di essa. Una PR difettosa non ti costa uno slot. Ti costa il resto del giro di CI del batch.
Questo rende il throughput della coda una funzione del tuo tasso di successo, ed è più ripida di quanto suggerisca l'intuizione. Modellalo: dimensione del batch N, ogni PR fallisce indipendentemente con probabilità p, e fai merge della sequenza di PR fino al primo fallimento. Il numero atteso di merge per giro è un valore atteso geometrico:
E[merges] = (1 − p) · (1 − (1 − p)^N) / p
Con N = 10:
Tasso di fallimento per PR p | Merge attesi per giro | Giri per smaltire 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 |
Due conseguenze che quasi tutti i team si immaginano al contrario:
- Una suite instabile è una tassa sul throughput, non una tassa di fastidio. La flakiness si somma direttamente a
p. Passare dal 5% di fallimenti reali al 5% reale + 5% instabile ti porta da 7,6 merge per giro a 5,9 — circa il 23% della tua capacità di merge, persa in test che non stavano testando nulla. - Aumentare la dimensione del batch è la leva sbagliata quando
pè alto. Un N più grande aiuta solo finché(1 − p)^Nresta vicino a 1. A p = 0,2 un batch da 10 produce 3,6 merge; la soluzione è abbassarep, non mettere in coda più ottimismo.
E il volume degli agenti spinge p nella direzione sbagliata — il risultato di DORA sulla stabilità è esattamente un'affermazione sul fatto che p sale.
L'aritmetica di capacità che nessuno fa
Fai questo conto su un tovagliolo prima di comprare runner. Con le assunzioni dichiarate, perché sono quelle che lo rendono sbagliato per il tuo team:
- Una suite da 45 minuti distribuita su 15 job paralleli costa comunque ~45 job-minuti. Il parallelismo compra latenza, mai costo. Prendi i job-minuti totali della tua suite da un'esecuzione recente, non dal job più lento.
- Con una merge queue ogni PR viene validata almeno due volte: una sul branch della PR, una dentro un merge group. Riesecuzioni e ricostruzioni dovute alla matematica di p sopra ne aggiungono altre. Metti a budget 2,5x come pavimento, non 1x.
- 40 PR x 2,5 validazioni x 45 job-min = 4.500 job-minuti. Distribuiti su 24 ore è una quantità banale. Ma non arrivano su 24 ore — arrivano in due o tre ore attorno all'orario di review. 4.500 job-min in una finestra di 2 ore richiedono ~37 job concorrenti sostenuti.
- I runner standard ospitati da GitHub limitano i job concorrenti totali a 20 (Free), 40 (Pro), 60 (Team) e 500 (Enterprise). Il conto del tovagliolo qui sopra supera in silenzio il piano Free e si siede sopra al Pro — durante il picco, che è l'unica finestra che conta.
- Un job in coda viene cancellato dopo 24 ore. Su una coda satura questo si manifesta come job che non sono mai partiti invece che come job falliti, il che è molto più difficile da notare e non fa squillare nessun cercapersone.
La morale non è "compra Enterprise". È che il vincolo è la concorrenza di picco, e il numero medio giornaliero che fa sembrare sana la tua pipeline è proprio il numero che nasconde il problema.
Quattro leve, ordinate per efficacia
I team le usano quasi esattamente nell'ordine inverso a quello utile.
1. Abbassa p (massima efficacia, costo minimo). Ogni punto di tasso di fallimento rimosso rende in modo non lineare nella matematica del batching. In concreto: fai eseguire agli agenti la suite veloce in locale prima di aprire una PR, richiedi un controllo pre-merge verde prima che a una PR sia permesso entrare in coda, e metti in quarantena i test instabili in modo aggressivo (sotto). Questo non costa alcuna potenza di calcolo.
2. Riduci il lavoro per validazione — selezione dei test. È l'unica leva che cambia l'esponente invece della costante. Invece di eseguire tutto, esegui ciò che la modifica può plausibilmente rompere:
- Develocity Predictive Test Selection costruisce un modello dalla cronologia dei tuoi Build Scan più pattern cross-progetto, e include un simulatore così puoi misurare il risparmio e il tasso di fallimenti mancati prima di attivarlo. Due dettagli di sicurezza che vale la pena rubare anche se non lo comprerai mai: puoi fissare test sempre da eseguire per pattern di classe o annotazione, e seleziona sempre i test recentemente instabili, perché un test instabile è per definizione imprevedibile a partire dalla modifica. Abbinalo a un'esecuzione dei "test rimanenti" post-merge o notturna, così nulla resta saltato per sempre.
- Bazel / Buck2 lo ottengono strutturalmente: il grafo di build sa quali target dipendono dai file modificati, quindi
bazel testsui target interessati è determinazione dei target gratis. - Nessun grafo di build? Python ha
pytest-testmon, che registra quali test coprono quale codice e riesegue solo quelli interessati. Jest offre--changedSincegià pronto.
Selezione dei test, tre ecosistemi
# 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. Usa la cache in modo aggressivo — e conosci la trappola. La cache remota memorizza i risultati delle azioni indicizzati su un hash di input, riga di comando e ambiente in whitelist. La documentazione di Bazel segnala il tranello: gli strumenti esterni al workspace non vengono tracciati, quindi due macchine con versioni diverse del compilatore possono ottenere un hit di cache su lavoro che avrebbe prodotto output differenti. Metti in whitelist ciò che conta con --action_env, e tieni l'accesso in scrittura solo alla CI — le macchine degli sviluppatori dovrebbero solo leggere:
Cache remota, scrittura solo dalla 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. Aggiungi runner (per ultimo). Costo lineare, beneficio lineare, e non fa nulla su p. Fallo dopo le prime tre, quando conosci davvero il tuo numero di concorrenza di picco.
La modalità di fallimento specifica dell'AI: l'agente si corregge i compiti da solo
Tutto quanto sopra vale per qualunque pipeline ad alto volume. Questo punto è specifico del codice scritto dagli agenti, ed è la lacuna che ha la maggior parte dei setup di CI.
Quando lo stesso agente scrive la correzione e il test per la correzione, "i test passano" porta molta meno informazione di quanto pensi. Due forme di fallimento compaiono di continuo:
- Il test verifica il comportamento dell'implementazione invece del requisito — passa perché è stato scritto leggendo il codice che sta testando.
- L'agente, quando un test preesistente fallisce, modifica il test per adattarlo al nuovo comportamento invece di correggere il codice. Il diff è verde e la regressione è andata in produzione. Quel comportamento ha un nome e una letteratura — vedi reward hacking negli agenti di coding per capire perché i modelli lo fanno e quali difese hanno un effetto misurato. Quello che segue è il controllo lato pipeline, non quello lato modello.
Il controllo strutturale a basso costo: un nuovo test deve fallire sul codice pre-modifica. È la disciplina red-green del TDD trasformata in un controllo automatico, ed è ciò che il mutation testing formalizza su scala. Non ti serve un framework di mutation testing per ottenere gran parte del valore — ti serve un singolo job di CI:
Controllo CI: i nuovi test devono fallire senza la modifica
#!/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."Due avvertenze oneste. Funziona solo quando sorgenti e test vivono in alberi separati, e un test che fallisce per il motivo sbagliato (errore di import, fixture mancante) supera il controllo. È un pavimento, non una dimostrazione — ma intercetta il modo più comune in cui una PR scritta da un agente è verde e inutile.
Il controllo complementare lato review è un revisore che non condivide il contesto dell'autore: vedi il revisore di codice AI ibrido e la GitHub Action di review delle PR.
Una quarantena dei test instabili che sopravvive al contatto con gli agenti
Dato che l'84% delle transizioni pass→fail sono instabilità, "rilancialo" è la risposta umana di default ed è la più costosa a disposizione — brucia un'intera validazione su un test che non ti avrebbe comunque detto nulla.
- Un test instabile è un test che produce risultati diversi sullo stesso commit. Esegui la suite N volte su un main immutato e registra i conteggi di fallimento per test. Quella è la tua prova; la memoria degli sviluppatori non lo è.
- Tutto ciò che sta sopra la tua soglia si sposta in un job non bloccante che continua a girare e a riportare. Smette immediatamente di bloccare i merge — quello è il guadagno di throughput — e resta visibile, così non diventa codice morto.
- Una quarantena senza tetto è un cimitero. Budget fisso (diciamo l'1% dei test), un nome attaccato, una data di scadenza. Sforare il budget significa che il prossimo test instabile sfratta il più vecchio, che torna a bloccare.
- È la regola controintuitiva che Develocity codifica: i test recentemente instabili vengono sempre selezionati, perché il modello non può prevederli dal diff. Selezionare via i test instabili nasconde proprio il segnale che ti serve per sistemarli.
- Ricalcola mensilmente il tasso di fallimento per PR e mettilo sulla dashboard accanto al throughput della merge queue. È l'unico numero che ti dice se la pipeline sta migliorando o se gli agenti stanno solo facendo più rumore.
Trappole che costano una giornata ciascuna
merge_groupnon è automatico. La documentazione di GitHub è esplicita: devi aggiungere l'eventomerge_groupai trigger del tuo workflow, altrimenti i tuoi controlli non partono mai dentro la coda e i merge falliscono per controlli obbligatori mancanti. È l'errore di configurazione della merge queue di gran lunga più comune.- La cancellazione della coda a 24 ore è silenziosa. Coda satura → i job spariscono invece di fallire. Fai alerting sulla profondità della coda, non solo sulle build rosse.
- I job a matrice hanno un tetto di 256 per esecuzione di workflow. Le matrici generate (un pattern comune nello scaffolding degli agenti) lo raggiungono senza un errore utile.
- Gli hit di cache tra macchine diverse possono essere sbagliati. Toolchain esterne non tracciate producono falsi hit. Fissa le toolchain, metti in whitelist l'ambiente con
--action_env. - La branch protection con pattern wildcard è incompatibile con le merge queue. Se le tue regole di protezione usano wildcard, la coda non si attiva.
- Le PR degli agenti che toccano la CI sono un confine di sicurezza, non una questione di stile. Una PR che può modificare i file di workflow può esfiltrare i segreti della CI — vedi issue GitHub → segreti CI e irrobustire le esecuzioni autonome.
L'inquadramento onesto
Non esiste una configurazione che renda una pipeline capace di assorbire output illimitato degli agenti. Quello che le leve sopra ti comprano è un sistema il cui costo cresce con la dimensione della modifica invece che con il numero di pull request — selezione dei test e caching sono sublineari, i runner sono lineari, e il batching della merge queue è sublineare solo finché il tuo tasso di successo resta alto.
Il che rimette il vincolo reale dove lo mette l'inquadramento DORA 2025: l'AI amplifica il sistema di delivery che già hai. Se batch piccoli, suite veloci e bassa instabilità erano già la tua pratica, gli agenti ti rendono molto più veloce. Se non lo erano, gli agenti lo scoprono.
Check yourself
0/6- La merge queue si rompe prima del budget dei runner. Il batching speculativo è una scommessa sul tuo tasso di successo, e una PR che fallisce invalida ogni gruppo dietro di essa.
- Calcola E[merge] = (1 − p)(1 − (1 − p)^N)/p una volta con i tuoi numeri reali. Trasforma 'la CI sembra lenta' in una decisione su quale leva tirare.
- La flakiness è throughput, non igiene. Ai tassi pubblicati da Google il rumore è più grande del segnale — l'84% delle transizioni pass→fail sono instabilità.
- Dimensiona per la concorrenza di picco, mai per la media giornaliera. Il numero giornaliero è quello che nasconde il problema, e una coda satura cancella i job in silenzio dopo 24 ore.
- La selezione dei test è l'unica leva sublineare: determinazione dei target con Bazel/Buck2, Develocity PTS con il suo simulatore, pytest-testmon, o jest --changedSince.
- Quando l'agente scrive sia la correzione sia il test, il verde significa meno di quanto pensi. Metti un controllo sul fatto che il test fallisca sul sorgente pre-modifica.
- L'inquadramento di DORA regge: l'AI amplifica il sistema di delivery che già hai. Batch piccoli, suite veloci e poca instabilità erano da sempre il consiglio — gli agenti hanno solo reso costoso ignorarlo.
Fonti e approfondimenti
- GitHub Docs — Managing a merge queue — gruppi speculativi, il requisito dell'evento
merge_group, impostazioni di concorrenza di build e limite di merge, comportamento in caso di fallimento/rimozione - GitHub Docs — Actions usage limits — tetti di job concorrenti per piano, la cancellazione della coda a 24 ore, il tetto di 256 job a matrice, timeout di job e workflow
- DORA — Accelerate State of DevOps Report 2024 — l'effetto modellato di un aumento del 25% nell'adozione dell'AI su stabilità e throughput della delivery
- DORA — State of AI-assisted Software Development 2025 — la tesi dell'"AI come amplificatore" e il focus sul sistema di delivery sottostante
- METR — Measuring the impact of early-2025 AI on experienced open-source developer productivity — lo studio randomizzato su 16 sviluppatori e 246 issue e il rallentamento del 19%
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — il tasso di ~1,5% di esecuzioni instabili e la cifra dell'84% sulle transizioni pass→fail
- Google Testing Blog — Where do our flaky tests come from? — analisi di follow-up sulle fonti di instabilità
- Develocity — Predictive Test Selection — il simulatore, i test sempre da eseguire, i profili di selezione, la selezione sempre attiva dei test recentemente instabili, la modalità test rimanenti
- Bazel — Remote caching — action cache e CAS, come vengono calcolati gli hash delle azioni,
--action_env, l'avvertenza sui falsi hit da toolchain esterne non tracciate - pytest-testmon — analisi d'impatto dei test basata sulla copertura, per Python
- Correlato su AILmanac: Il collo di bottiglia della verifica — il lato umano/review dello stesso vincolo
- Correlato su AILmanac: Open Code Review: il revisore AI ibrido — un revisore che non condivide il contesto dell'autore
Prossimi passi
- Il collo di bottiglia della verifica — perché la review, e non la scrittura, è diventata il vincolo
- Una GitHub Action che rivede ogni PR — il workflow concreto da mettere davanti a questa pipeline
- Valutare il tuo agente AI — il controllo di eval che appartiene alla stessa esecuzione di CI