Déclencheurs et filtres
Pourquoi
Le workflow de la leçon précédente réagit à push et à pull_request, sans plus de précision. Il fonctionne, mais il pose trois questions qu'un pipeline réel ne peut pas laisser au hasard.
Combien de fois ? Une branche poussée puis proposée en demande de fusion déclenche deux exécutions par commit : une pour la poussée, une pour la demande. Le double du temps de runner, et deux checks qui disent presque la même chose.
Quoi exactement ? Pour une demande de fusion, GitHub ne vérifie pas le commit que vous avez poussé, mais un commit qui n'existe dans aucune branche. Savoir lequel, c'est savoir ce que le check vert garantit.
Avec quel effet ? Certains déclencheurs s'exécutent avec des droits d'écriture et des secrets, d'autres non. Un mauvais choix de déclencheur est à l'origine de la plupart des compromissions de pipelines de ces dernières années.
Cette leçon répond aux deux premières questions, prépare la troisième (traitée à la leçon 11), et ajoute deux besoins courants : lancer un workflow à la main avec des paramètres, et le lancer à heure fixe.
Les concepts
Un événement, un commit, une référence
Chaque événement porte deux informations qui décident de ce que le workflow vérifie : le commit (GITHUB_SHA) et la référence (GITHUB_REF). Ce sont elles qu'utilise actions/checkout par défaut.
| Événement | GITHUB_SHA | GITHUB_REF | Workflow lu depuis |
|---|---|---|---|
push | le dernier commit poussé | la branche ou l'étiquette poussée | ce commit |
pull_request | le commit de fusion temporaire | refs/pull/<n>/merge | ce commit de fusion |
workflow_dispatch | le dernier commit de la branche choisie | cette branche | ce commit |
schedule | le dernier commit de la branche par défaut | la branche par défaut | ce commit |
pull_request_target | le dernier commit de la branche par défaut | la branche par défaut | la branche par défaut |
La dernière ligne est une exception majeure, détaillée à la leçon 11 : pull_request_target exécute le workflow de la branche par défaut, avec des droits étendus, en réaction à une demande de fusion.
Le commit de fusion d'une demande de fusion
Quand une demande de fusion est ouverte, GitHub calcule en permanence ce que donnerait sa fusion dans la branche cible, et range ce résultat sous la référence refs/pull/<n>/merge. C'est ce commit-là que vérifie un workflow déclenché par pull_request.
gitGraph
commit id: "A"
commit id: "B"
branch fonctionnalite
commit id: "C"
commit id: "D"
checkout main
commit id: "E (un collègue)"
checkout fonctionnalite
merge main id: "M = refs/pull/7/merge" type: HIGHLIGHT
Le check vert d'une demande de fusion signifie donc : « la fusion de D dans E passe les tests », et pas seulement « D passe les tests ». C'est exactement la vérification qui révèle les conflits sémantiques étudiés au cours précédent : deux changements corrects séparément, incorrects ensemble. Deux conséquences :
- si la branche cible avance, le résultat calculé change, mais aucun nouveau run n'est lancé : le check vert peut vieillir. Les files de fusion (merge queues, événement
merge_group) existent pour fermer cette fenêtre ; - si la demande de fusion a un conflit, GitHub ne peut pas calculer le commit de fusion, et aucun workflow
pull_requestne s'exécute tant que le conflit n'est pas résolu.
Types d'activité
Beaucoup d'événements regroupent plusieurs actions, appelées types d'activité. Sans précision, pull_request ne réagit qu'à trois d'entre eux : opened (ouverture), synchronize (nouveau commit poussé sur la branche) et reopened. Changer le titre, ajouter une étiquette ou passer de brouillon à prête ne relance rien, sauf si on le demande :
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]Trois niveaux pour « ne pas exécuter »
On peut empêcher du travail à trois niveaux, et ils n'ont pas le même effet sur les checks :
| Niveau | Mécanisme | Effet sur un check exigé |
|---|---|---|
| Workflow | filtre branches, paths, mention [skip ci] | aucun run : le check reste en attente et bloque la fusion |
| Job | if: sur le job | job ignoré, considéré comme réussi |
| Étape | if: sur l'étape | étape ignorée, le job continue |
Ce tableau explique un piège très fréquent, développé plus bas : filtrer par chemins un workflow dont le check est exigé bloque toutes les demandes de fusion qui ne touchent pas ces chemins.
En pratique
Ne vérifier qu'une fois
Avec on: [push, pull_request], chaque commit d'une branche proposée est vérifié deux fois. La configuration la plus courante sépare les rôles :
on:
push:
branches: [main]
pull_request:pull_requestvérifie tout ce qui est proposé, sur le commit de fusion ;pushsurmainvérifie ce qui a été fusionné, le commit qui partira en production.
Une branche poussée sans demande de fusion n'est plus vérifiée. C'est généralement voulu : on ouvre une demande de fusion en brouillon pour obtenir la CI sur un travail en cours. Si l'équipe préfère vérifier toutes les branches, on garde push sans filtre et on ajoute un garde-fou de concurrence (plus bas) ; on paiera alors le double sur les branches proposées.
Filtrer par branche et par étiquette
Les filtres branches et tags acceptent des motifs :
| Motif | Correspond à | Ne correspond pas à |
|---|---|---|
main | main | main-v2 |
release/* | release/2026.10 | release/2026/10 (* ne traverse pas /) |
release/** | release/2026/10 | |
v[0-9]+.* | v1.0, v12.3.1 | version-1 (+ : un ou plusieurs du caractère précédent) |
!release/**-rc | exclut release/2.0-rc |
En YAML, *, [ et ! sont des caractères spéciaux : un motif qui commence par l'un d'eux doit être entre guillemets ("*-rc", "!release/**-rc"), sinon le fichier ne s'analyse pas. L'ordre compte : un motif négatif (!) placé après un positif exclut ; un positif placé après un négatif réinclut. On ne peut pas combiner branches et branches-ignore pour un même événement ; pour inclure et exclure, on utilise branches avec des motifs !, précédés d'au moins un motif positif.
Pour un workflow de publication déclenché par une étiquette de version :
on:
push:
tags: ["v*"]Une règle peu intuitive s'applique ici : si un workflow ne définit que branches, il ne réagit plus aux étiquettes, et inversement. Sans aucun des deux, il réagit aux deux. Le workflow CI de Signalements, avec branches: [main], ne se déclenche donc pas quand on pousse l'étiquette v1.2.0 : c'est ce que l'on veut, la publication aura son propre workflow (leçon 6).
Warning
GitHub ne crée aucun événement pour les étiquettes quand plus de trois sont poussées d'un coup (git push --tags après une migration, par exemple). Poussez les étiquettes de version une par une.
Filtrer par chemin
Le filtre paths restreint le workflow aux poussées ou demandes de fusion qui modifient certains fichiers :
on:
pull_request:
paths:
- "**.py"
- "requirements*.txt"
- ".github/workflows/ci.yml"Le workflow s'exécute si au moins un fichier modifié correspond. paths-ignore fait l'inverse, avec une règle symétrique : le workflow est sauté seulement si tous les fichiers modifiés correspondent. Quand branches et paths sont définis ensemble, les deux doivent être satisfaits. Pensez à inclure le fichier du workflow lui-même dans paths : sinon, une modification du pipeline n'est pas testée par le pipeline.
Reste à savoir ce que GitHub appelle « fichiers modifiés », et la réponse dépend de l'événement : une différence à deux points pour une poussée (le commit poussé contre le précédent sommet de la branche), une différence à trois points pour une demande de fusion (la branche contre le point où elle a quitté sa cible). La distinction se voit sur un exemple. Une branche doc-api ajoute un fichier de documentation ; pendant ce temps, main reçoit un README.md :
$ git diff --name-only main..doc-api # deux points
README.md
docs/api.md
$ git diff --name-only main...doc-api # trois points
docs/api.md
La différence à deux points compare deux sommets, et attribue à la branche le README.md qu'elle n'a pas touché (il apparaît parce que la branche n'a pas cette modification de main). La différence à trois points ne garde que ce que la branche a réellement changé depuis son départ. C'est la bonne question pour une demande de fusion : « qu'est-ce que cette proposition modifie ? ».
Quelques limites documentées : une poussée de plus de 1 000 commits déclenche toujours le workflow ; si le calcul de la différence échoue par dépassement de délai, aussi ; et seuls les 3 000 premiers fichiers modifiés sont examinés.
Caution
Un workflow sauté par un filtre paths ou branches ne crée aucun check. Si ce check est exigé par une règle de protection, la demande de fusion attend un statut qui ne viendra jamais. Ne filtrez jamais par chemins un workflow dont le check est exigé. Faites tourner le workflow, et décidez dans le workflow, au niveau du job, s'il y a du travail (un job ignoré par if: compte comme réussi). La leçon 3 montre comment calculer les fichiers modifiés et en tirer une condition.
Exécuter à la main, avec des paramètres
workflow_dispatch ajoute un bouton Run workflow dans l'onglet Actions, et une entrée dans l'API. Il accepte jusqu'à 25 paramètres typés : string, boolean, number, choice (une liste fermée) et environment (un environnement du dépôt, leçon 9). Voici un workflow de maintenance pour la recette de Signalements, qui sera branché à une vraie base à la leçon 9 :
name: Purger la recette
on:
workflow_dispatch:
inputs:
simulation:
description: "Afficher ce qui serait supprimé, sans rien supprimer"
type: boolean
default: true
anciennete:
description: "Supprimer les signalements plus vieux que (jours)"
type: number
default: 30
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
purger:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Simulation
if: inputs.simulation
env:
JOURS: ${{ inputs.anciennete }}
run: |
echo "Simulation : suppression des signalements de plus de $JOURS jours"
- name: Suppression
if: ${{ !inputs.simulation }}
env:
JOURS: ${{ inputs.anciennete }}
run: |
echo "Suppression des signalements de plus de $JOURS jours"Plusieurs détails comptent :
default: truepoursimulation: l'action par défaut est sans danger. Une opération destructrice doit demander un geste explicite.inputs.simulationest un vrai booléen. Le même paramètre est aussi disponible dansgithub.event.inputs.simulation, mais converti en chaîne ("true"). Une conditiongithub.event.inputs.simulation == truecompare alors une chaîne à un booléen, et la leçon 3 montre pourquoi elle est toujours fausse. Utilisez toujours le contexteinputs.${{ !inputs.simulation }}: une expression qui commence par!doit être entourée de${{ }}. Écriteif: !inputs.simulation, YAML lit!inputs.simulationcomme une étiquette de type, et la condition se retrouve vide (actionlint le signale :string should not be empty).- Le paramètre passe par
env:, pas directement dans le script. Pour un nombre ce n'est pas indispensable, mais c'est l'habitude qui protège des injections dès que le paramètre est une chaîne libre (leçon 11).
Le bouton n'apparaît que si le fichier est présent sur la branche par défaut. Une fois là, on peut lancer le workflow sur n'importe quelle branche, depuis l'interface ou le terminal :
$ gh workflow run purge-recette.yml -f simulation=false -f anciennete=60
$ gh workflow run purge-recette.yml --ref ma-branche # le workflow tel qu'il est sur ma-branche
actionlint connaît les paramètres déclarés et leur type. Une faute de frappe dans un nom de paramètre est détectée avant la poussée :
$ actionlint .github/workflows/d.yml
.github/workflows/d.yml:21:30: property "cibel" is not defined in object type {cible: string; simulation: bool} [expression]
|
21 | - run: echo "cible ${{ inputs.cibel }}"
| ^~~~~~~~~~~~
Planifier
schedule déclenche un workflow à heure fixe, avec la syntaxe cron POSIX à cinq champs (minute, heure, jour du mois, mois, jour de la semaine). Un usage utile pour Signalements : relancer la CI chaque lundi matin sur main, même sans changement de code, pour détecter tôt ce qui casse « tout seul » (une dépendance retirée, une image de runner modifiée, un certificat expiré).
on:
schedule:
- cron: "30 5 * * 1"
timezone: "Europe/Paris"Par défaut, l'heure est en UTC. La clé timezone, avec un nom de fuseau IANA, fait suivre l'heure légale. Sans elle, une tâche réglée sur 6 h UTC change d'heure locale deux fois par an :
$ python3 - <<'EOF'
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
paris = ZoneInfo("Europe/Paris")
for jour in ["2026-10-23", "2026-10-26"]:
utc = datetime.fromisoformat(jour + "T06:00:00").replace(tzinfo=timezone.utc)
print(f"{jour} 06:00 UTC -> {utc.astimezone(paris):%H:%M} à Paris")
EOF
2026-10-23 06:00 UTC -> 08:00 à Paris
2026-10-26 06:00 UTC -> 07:00 à Paris
Pour un rapport envoyé à une équipe, ou une purge qui doit tomber hors des heures ouvrées, ce décalage compte.
Les règles de schedule à connaître :
- le workflow planifié s'exécute uniquement depuis la branche par défaut, sur son dernier commit ;
- l'intervalle minimal est de 5 minutes ;
- l'exécution peut être retardée aux heures de forte charge, et GitHub cite explicitement le début de chaque heure : préférez
30 5à0 6. En cas de charge très forte, une exécution planifiée peut même être abandonnée : ne confiez jamais àscheduleune tâche qui ne supporte pas d'être sautée ; - dans un dépôt public, les workflows planifiés sont désactivés après 60 jours sans activité sur le dépôt ;
- les raccourcis
@dailyou@weeklyne sont pas acceptés.
Les autres déclencheurs utiles
release: publication d'une version dans l'interface de GitHub (typespublished,released...). Utile si l'équipe publie ses versions par l'interface plutôt que par étiquette.merge_group: exécution dans une file de fusion. Si le dépôt utilise une file de fusion, les workflows exigés doivent écouter cet événement en plus depull_request, sinon la file attend indéfiniment.workflow_run: s'exécute quand un autre workflow se termine. Il sert à enchaîner un travail privilégié (commenter une demande de fusion, publier) après un travail non privilégié. Il s'exécute dans le contexte de la branche par défaut, avec des droits étendus : un usage à risque, étudié à la leçon 11.repository_dispatch: déclenchement par un appel d'API (POST /repos/<propriétaire>/<dépôt>/dispatches), pour qu'un système externe lance un workflow.issue_comment,issues,discussion: réaction à l'activité des tickets. Leur contenu est écrit par n'importe qui, ce qui en fait des vecteurs d'injection classiques (leçon 11).
La concurrence
Pousser trois commits en dix minutes sur une demande de fusion lance trois runs, dont deux sont périmés avant même d'avoir fini. Le bloc concurrency regroupe les runs sous une clé et décide quoi faire des runs d'une même clé :
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}group: la clé. Ici, le nom du workflow suivi du numéro de la demande de fusion, ou à défaut de la référence (refs/heads/main). Chaque demande de fusion a son groupe,maina le sien. Les noms de groupe ne tiennent pas compte de la casse.cancel-in-progress: une expression. Sur une demande de fusion, un nouveau commit annule le run en cours, devenu inutile. Surmain, on laisse finir : chaque commit fusionné mérite un verdict, et c'est souvent lui qu'on déploiera.
Même sans cancel-in-progress, un groupe n'admet par défaut qu'un seul run en cours et un seul en attente : si un troisième arrive, celui qui attendait est annulé et remplacé. Pour la CI de main, c'est acceptable (le plus récent l'emporte). Pour une file de déploiements qui doivent tous passer, dans l'ordre, GitHub accepte depuis mai 2026 une file plus longue :
concurrency:
group: deploiement-production
queue: max # jusqu'à 100 runs en attente, traités dans l'ordrequeue: max est incompatible avec cancel-in-progress: true. Et c'est une nouveauté que les outils n'ont pas tous rattrapée :
$ actionlint .github/workflows/t.yml
.github/workflows/t.yml:9:3: unexpected key "queue" for "concurrency" section. expected one of "cancel-in-progress", "group" [syntax-check]
|
9 | queue: max
| ^~~~~~
actionlint 1.7.12 date de mars 2026 et ignore cette clé. Si vous l'utilisez, désactivez cette vérification pour ce fichier dans la configuration d'actionlint, en le documentant, plutôt que de renoncer à la validation.
Ce qui ne déclenche rien
Deux comportements de la plateforme évitent des boucles infinies, et surprennent la première fois.
Une poussée, un run. Pousser plusieurs commits d'un coup crée un seul événement push, pour le dernier commit. Les commits intermédiaires ne sont jamais vérifiés individuellement.
Ce que fait le GITHUB_TOKEN ne déclenche pas de workflow. Un commit poussé, une étiquette créée ou un ticket ouvert par un workflow avec le jeton automatique ne lance aucun autre workflow, sauf workflow_dispatch et repository_dispatch. Les demandes de fusion ouvertes ou mises à jour avec ce jeton sont un cas à part : leurs runs pull_request sont créés, mais attendent l'approbation d'une personne ayant les droits d'écriture.
Le site que vous lisez illustre les deux. Son workflow de déploiement termine en poussant, avec le GITHUB_TOKEN, un commit Deploy <sha> qui met à jour l'image à déployer (leçon 9). Voici l'historique de main et la liste des runs, telles qu'elles se présentaient le 1er octobre 2026 :
$ git log --oneline -9 origin/main
aeb9a86 Deploy ff1c4b7
ff1c4b7 Cours CI/CD : corrections de relecture (6 leçons) ; passage en propriété Lyneko, retrait de la licence CC
63a3b7a Cours CI/CD : les principes : glossaire, outils, lab, quiz, billet, publication
a62fde4 Cours CI/CD : les principes : leçon 6 (mesurer avec DORA)
b5b3931 Cours CI/CD : les principes : leçon 5 (sécuriser le pipeline)
e08c1aa Cours CI/CD : les principes : leçon 4 (livraison et déploiement continus)
07a3578 Cours CI/CD : les principes : leçon 3 (tests et qualité dans le pipeline)
7d4f9f5 Cours CI/CD : les principes : leçon 2 (anatomie d'un pipeline)
8d751ca Cours CI/CD : les principes : présentation et leçon 1 (intégrer en continu)
$ gh run list --limit 3 --json createdAt,event,headSha,conclusion \
--jq '.[] | "\(.createdAt) \(.event) \(.headSha[0:7]) \(.conclusion)"'
2026-10-01T15:59:14Z push ff1c4b7 success
2026-10-01T14:15:23Z push d35ec8d success
2026-10-01T13:44:21Z push 5f16a62 success
Le dernier run porte sur ff1c4b7 : les huit commits du cours, de 8d751ca à ff1c4b7, poussés ensemble, ont produit un run. Et le commit aeb9a86 Deploy ff1c4b7, poussé par le workflow lui-même, n'en a produit aucun : sans cette règle, il aurait relancé le workflow, qui aurait poussé un nouveau commit, et ainsi de suite.
Si l'on veut réellement qu'un workflow en déclenche un autre par une poussée, il faut un autre jeton : celui d'une GitHub App (leçon 9), ou workflow_dispatch appelé explicitement.
Sauter la CI volontairement
Une mention [skip ci], [ci skip], [no ci], [skip actions] ou [actions skip] dans le message du commit poussé (ou du dernier commit d'une demande de fusion) empêche les workflows push et pull_request de démarrer. C'est utile pour une correction de faute de frappe dans un document. Mais le tableau des trois niveaux s'applique : aucun run, donc les checks exigés restent en attente, et la demande de fusion est bloquée jusqu'au prochain commit sans la mention.
Le workflow de Signalements, à ce stade
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
schedule:
- cron: "30 5 * * 1"
timezone: "Europe/Paris"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
verifier:
name: Lint et tests
# ... inchangé depuis la leçon 1Sous le capot
À chaque événement, GitHub parcourt les fichiers de .github/workflows/ au commit concerné (sauf pour les exceptions du premier tableau), évalue la section on: de chacun, et crée un run pour chaque workflow qui correspond. Le filtrage par branches et paths se fait à cette étape, côté serveur, avant toute réservation de runner : un workflow filtré ne coûte rien.
L'événement complet est ensuite transmis au runner sous la forme du contenu JSON du webhook, écrit dans un fichier dont le chemin est donné par la variable GITHUB_EVENT_PATH, et exposé dans les expressions sous github.event. Tout ce que GitHub sait de l'événement y est : titre et corps de la demande de fusion, auteur, branches, commits. Pour une poussée, ce contenu ne liste pas les fichiers ajoutés ou modifiés de chaque commit (contrairement au webhook classique) : il faut les demander à l'API ou à Git.
La différence à trois points d'une demande de fusion est calculée contre la base de fusion, le dernier ancêtre commun de la branche et de sa cible, celui que donne git merge-base main doc-api. Le commit de fusion refs/pull/<n>/merge est recalculé par GitHub à chaque poussée sur la branche ; c'est une référence réelle, que l'on peut récupérer localement :
$ git fetch origin refs/pull/7/merge:pr-7-fusion
C'est le moyen de reproduire sur sa machine exactement ce qu'a testé un run de demande de fusion.
Pièges courants
mapping values are not allowed in this context. La typographie française place une espace avant le deux-points, et YAML interprète « : » comme le séparateur d'une clé et de sa valeur, au milieu d'une valeur non citée :
$ actionlint purge-casse.yml
purge-casse.yml:31:30: could not parse as YAML: mapping values are not allowed in this context [syntax-check]
|
31 | run: echo "Simulation : suppression des signalements de plus de $JOURS jours"
| ^
Les guillemets doubles appartiennent ici à la commande shell, pas à YAML : pour YAML, la valeur commence par echo et n'est pas citée. Écrivez les commandes en bloc littéral (run: | puis la commande sur la ligne suivante), qui ne connaît pas ce problème.
Le bouton Run workflow n'apparaît pas. Le fichier n'est pas encore sur la branche par défaut, ou il ne déclare pas workflow_dispatch.
Le workflow planifié ne s'est pas exécuté. Le fichier n'est pas sur la branche par défaut ; ou le dépôt public est inactif depuis 60 jours (le workflow apparaît comme désactivé dans l'onglet Actions) ; ou l'exécution a été retardée, voire abandonnée, à une heure de forte charge.
Le workflow ne réagit pas aux étiquettes. Il définit branches sans tags. Ou plus de trois étiquettes ont été poussées en même temps.
Une demande de fusion n'a aucun run. Elle a un conflit avec sa branche cible : résolvez-le, et le run démarre.
Un check exigé reste « Expected, waiting for status to be reported ». Un filtre paths ou branches, ou une mention [skip ci], a empêché le run. Voir l'encadré sur les filtres par chemins.
github.event.inputs.x == true n'est jamais vrai. Le contexte github.event.inputs contient des chaînes. Utilisez inputs.x.
Sécurité
Le déclencheur décide qui peut lancer le workflow et avec quels droits, et c'est la première ligne d'une analyse de risque :
pushetpull_requestdepuis une branche du dépôt : seules les personnes qui ont le droit d'écrire dans le dépôt peuvent les provoquer.pull_requestdepuis une bifurcation (fork) : n'importe qui peut le provoquer. GitHub l'exécute avec un jeton en lecture seule et sans les secrets du dépôt ; pour un premier contributeur, une approbation manuelle est demandée par défaut avant l'exécution.pull_request_target,issue_comment,workflow_run: provoqués par n'importe qui sur un dépôt public, mais exécutés avec les secrets et un jeton en écriture. C'est la combinaison exploitée dans les compromissions d'Ultralytics (2024), de Trivy et de TanStack (2026). Depuis septembre 2026, GitHub propose des règles d'exécution des workflows par organisation, et désactiverapull_request_targetpar défaut sur les dépôts publics qui n'ont pas défini de règle, à partir du 2 novembre 2026. La leçon 11 détaille ces attaques et leurs parades.schedules'exécute sous l'identité de la dernière personne qui a modifié la lignecron: si cette personne quitte l'équipe, ses notifications et son identité restent attachées au workflow.
Une règle simple en découle : un workflow qui manipule des secrets ou écrit dans le dépôt ne doit réagir qu'à des événements que seules des personnes de confiance peuvent provoquer.
En production
La file de fusion. Sur un dépôt très actif, le check vert d'une demande de fusion vieillit vite : main avance, et le commit de fusion vérifié n'est plus celui qui sera produit. La file de fusion de GitHub (merge queue) teste chaque demande de fusion par-dessus celles qui la précèdent dans la file, avant de les fusionner : c'est l'intégration continue appliquée à la fusion elle-même. Elle exige que les workflows exigés écoutent merge_group. Elle est disponible pour les dépôts publics appartenant à une organisation, et pour les dépôts privés seulement avec GitHub Enterprise Cloud.
Le coût des planifications. Une tâche toutes les 5 minutes, de 1 minute facturée, représente 8 640 minutes par mois : plus de quatre fois les 2 000 minutes du plan gratuit d'une organisation, pour un dépôt privé. Avant de planifier, demandez-vous si un déclenchement par événement (repository_dispatch depuis le système concerné) ne ferait pas mieux.
Annuler sans casser. cancel-in-progress: true sur un workflow qui déploie peut interrompre un déploiement en plein milieu, entre deux étapes. Gardez l'annulation pour les vérifications, et protégez les déploiements par un groupe de concurrence dédié au niveau du job, sans annulation (c'est ce que fait le workflow de ce site : cancel-in-progress: false sur le job de déploiement).
Filtrer les gros dépôts. Dans un dépôt qui contient plusieurs applications (monorepo), les filtres paths évitent de tout reconstruire à chaque commit. Ils sont légitimes pour des workflows non exigés ; pour les checks exigés, préférez un workflow unique qui calcule les parties touchées et ignore les jobs inutiles (leçons 3 et 4).
Exercices
1. Un dépôt a ce déclencheur. Pour chacune des actions suivantes, dites si le workflow s'exécute : (a) poussée d'un commit modifiant app.py sur main ; (b) poussée de l'étiquette v2.0.0 ; (c) poussée sur main d'un commit qui ne modifie que README.md ; (d) poussée sur release/2026/10 d'un commit modifiant app.py.
on:
push:
branches: [main, "release/*"]
paths: ["**.py"]Solution
(a) Oui : la branche et le chemin correspondent. (b) Non : le workflow ne définit que branches, il ne réagit donc à aucune étiquette (et les filtres paths ne s'appliquent de toute façon pas aux étiquettes). (c) Non : aucun fichier modifié ne correspond à **.py. (d) Non : release/* ne traverse pas les /, il faudrait release/**.
2. Votre équipe exige le check Lint et tests avant fusion. Un collègue ajoute paths-ignore: ["docs/**", "**.md"] au workflow CI « pour économiser des minutes ». Que se passe-t-il pour une demande de fusion qui ne modifie que docs/api.md ? Proposez une alternative.
Solution
Tous les fichiers modifiés correspondent à paths-ignore : le workflow n'est pas créé, aucun check Lint et tests n'est publié, et la demande de fusion attend indéfiniment ce check. Alternative : retirer paths-ignore, laisser le workflow démarrer, et ajouter au job un if: qui l'ignore quand seuls des fichiers de documentation ont changé (un job ignoré compte comme réussi). La leçon 3 montre comment produire cette condition.
3. Écrivez le déclencheur d'un rapport hebdomadaire envoyé chaque vendredi à 17 h 15, heure de Paris, toute l'année.
Solution
on:
schedule:
- cron: "15 17 * * 5"
timezone: "Europe/Paris"Sans timezone, il faudrait choisir entre 15 15 (juste l'été) et 15 16 (juste l'hiver). La minute 15 évite au passage le pic de charge du début d'heure.
4. Sur une demande de fusion, vous poussez un commit A, puis B trente secondes plus tard, puis C trente secondes après, alors que chaque run dure trois minutes. Décrivez ce qui se passe (a) sans bloc concurrency ; (b) avec le bloc de cette leçon ; (c) avec le même groupe mais cancel-in-progress: false.
Solution
(a) Trois runs complets, en parallèle : neuf minutes de runner, dont six pour des commits déjà dépassés. (b) B annule A, puis C annule B : un seul run va jusqu'au bout, celui de C. (c) A s'exécute ; B attend ; quand C arrive, B (en attente) est annulé et remplacé par C, qui démarre après A. Deux runs complets, A et C, et le verdict sur C arrive plus tard qu'en (b).
5. Votre workflow de publication réagit à push sur tags: ["v*"]. Après une migration de dépôt, vous poussez d'un coup les 40 étiquettes historiques avec git push --tags. Combien de runs de publication démarrent ? Est-ce un problème ?
Solution
Aucun : GitHub ne crée pas d'événement pour les étiquettes quand plus de trois sont poussées à la fois. Ici, c'est plutôt une chance (republier 40 versions historiques serait indésirable). Mais la même règle fait qu'une poussée groupée de trois versions nouvelles et d'une ancienne ne publie rien du tout : poussez toujours une nouvelle étiquette de version seule (git push origin v2.1.0).
Récapitulatif
- Chaque événement fixe un commit (
GITHUB_SHA) et une référence (GITHUB_REF). Pourpull_request, c'est le commit de fusionrefs/pull/<n>/merge: on teste le résultat de la fusion. Pas de run en cas de conflit. pushsurmainpluspull_requestvérifie tout, une seule fois.branches,tags,pathsfiltrent avant la création du run. Un workflow filtré ne publie aucun check : ne filtrez pas un workflow exigé, filtrez ses jobs.- Différence à deux points pour les poussées, à trois points pour les demandes de fusion.
workflow_dispatch: paramètres typés, lus dansinputs, valeurs par défaut sans danger, fichier sur la branche par défaut.schedule: branche par défaut, UTC sauftimezone, retards possibles, désactivé après 60 jours d'inactivité sur un dépôt public.concurrencyannule les runs périmés des demandes de fusion et sérialise les déploiements, sans jamais en interrompre un.- Les actions du
GITHUB_TOKENne déclenchent pas de workflow ; une poussée de plusieurs commits produit un seul run.
Pour aller plus loin
- GitHub Docs, Events that trigger workflows : la liste complète, avec
GITHUB_SHAetGITHUB_REFpour chaque événement. - GitHub Docs, Managing a merge queue : configurer une file de fusion.
- Leçon suivante : Expressions, contextes et sorties.
Sources
- GitHub Docs, Events that trigger workflows
- GitHub Docs, Workflow syntax : on.push, on.pull_request, paths, branches, schedule
- GitHub Docs, Control the concurrency of workflows and jobs
- GitHub Docs, Skipping workflow runs
- GitHub Docs, Manually running a workflow (gh workflow run)
- GitHub Changelog, Concurrency groups now allow larger queues (7 mai 2026)
- GitHub Docs, Pull request refs and merge branches
- POSIX, crontab : format des entrées
- Documentation Git, git-diff : syntaxes A..B et A...B
- YAML 1.2.2, scalaires simples et indicateur « : »