Par défaut, GitHub Actions exécute chaque déclenchement indépendamment. Si vous
poussez 5 commits rapidement, vous obtenez 5 workflows en parallèle.
C'est du gaspillage de ressources et, pire, une source de conflits de
déploiement. Le bloc concurrency règle les deux.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Annuler automatiquement les runs obsolètes avec
cancel-in-progress - Définir un groupe de concurrence par branche, par PR ou par environnement
- Sérialiser les déploiements pour éviter deux déploiements simultanés
- Protéger la branche
maindes annulations intempestives - Éviter les pièges : groupe trop large, trop spécifique, ou dangereux
Le problème
Section intitulée « Le problème »Sans garde-fou, chaque push lance son propre workflow. Sur une branche active, les premiers runs sont déjà obsolètes avant même de finir : seul le dernier commit compte.
Commit 1 → Workflow 1 (en cours)Commit 2 → Workflow 2 (en cours) ← Inutile, commit 2 sera écraséCommit 3 → Workflow 3 (en cours) ← Inutile aussiCommit 4 → Workflow 4 (en cours) ← Inutile aussiCommit 5 → Workflow 5 (en cours) ← Seul celui-ci compte vraimentSolution : annuler automatiquement les runs obsolètes.
Syntaxe de base
Section intitulée « Syntaxe de base »Le bloc concurrency se déclare au niveau du workflow (ou d'un job). Deux
propriétés suffisent : un groupe et la décision d'annuler ou non.
name: CI
on: [push, pull_request]
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
jobs: build: runs-on: ubuntu-24.04 steps: [...]Propriétés
Section intitulée « Propriétés »group accepte n'importe quelle chaîne, y compris des expressions
${{ }} évaluées au démarrage du run : c'est ce qui permet d'obtenir un groupe
par branche ou par PR. cancel-in-progress est le seul commutateur de
comportement, et il ne concerne que les runs déjà en cours du même groupe.
| Propriété | Description |
|---|---|
group | Identifiant du groupe de concurrence |
cancel-in-progress | Annuler les runs en cours du même groupe |
Groupes de concurrence
Section intitulée « Groupes de concurrence »Tout se joue dans la composition du groupe. Deux runs qui partagent un groupe identique entrent en concurrence ; sinon ils s'ignorent. Voici les quatre découpages les plus utiles.
Par branche
Section intitulée « Par branche »C'est le découpage à retenir par défaut. github.ref contient la référence
complète (refs/heads/main), donc deux branches ne se retrouvent jamais dans le
même groupe, et github.workflow évite qu'un workflow en annule un autre.
# Annule les runs précédents sur la même brancheconcurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: trueUn push sur main annule les runs précédents sur main, mais pas ceux sur
develop.
Ce groupe s'appuie sur le numéro de la PR plutôt que sur la branche. Utile
quand le workflow réagit aussi à des événements de PR (réouverture, changement
de cible) où github.ref ne désigne pas la même chose d'un événement à
l'autre. Sur un run déclenché hors PR, l'expression est vide et tous ces runs
tombent dans un groupe commun.
# Annule les runs précédents sur la même PRconcurrency: group: pr-${{ github.event.pull_request.number }} cancel-in-progress: truePar workflow
Section intitulée « Par workflow »Un groupe constant, sans expression, met en file toutes les exécutions du workflow quelle que soit la branche. C'est la configuration d'un déploiement : un seul run à la fois, et les suivants attendent au lieu d'être annulés.
# Un seul run de ce workflow à la fois (global)concurrency: group: deploy-production cancel-in-progress: false # Attendre au lieu d'annulerPar environnement
Section intitulée « Par environnement »Ici le groupe dépend d'un paramètre du run : deux déploiements vers des
environnements différents avancent en parallèle, deux déploiements vers le même
environnement se sérialisent. L'expression || 'staging' fournit une valeur de
repli quand le workflow n'est pas déclenché manuellement, sans quoi le groupe
serait vide.
# Un seul déploiement par environnementconcurrency: group: deploy-${{ github.event.inputs.environment || 'staging' }} cancel-in-progress: falsePatterns courants
Section intitulée « Patterns courants »Les quatre configurations qui suivent couvrent la quasi-totalité des besoins. Elles se distinguent par une seule question : le travail en cours est-il jetable parce qu'un commit plus récent le remplace, ou irremplaçable parce qu'il modifie un système ?
CI classique
Section intitulée « CI classique »Annuler les runs obsolètes pour économiser les ressources : une compilation et des tests sont rejouables, rien n'est perdu à les interrompre. Sur un dépôt actif, ce seul bloc supprime l'essentiel des minutes gaspillées.
name: CI
on: push: branches: [main, develop] pull_request: branches: [main]
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
permissions: {}
jobs: build: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: npm ci && npm testDéploiement séquentiel
Section intitulée « Déploiement séquentiel »Ne pas annuler, mais attendre que le déploiement précédent soit terminé : un script de déploiement coupé en plein vol laisse l'infrastructure dans un état intermédiaire, avec la moitié des instances à jour. La mise en file garantit qu'un seul déploiement écrit à la fois, et que le dernier commit passe en dernier.
name: Deploy
on: push: branches: [main]
concurrency: group: deploy-production cancel-in-progress: false # Attendre, ne pas annuler
permissions: {}
jobs: deploy: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: ./deploy.shDifférencier CI et déploiement
Section intitulée « Différencier CI et déploiement »Un workflow qui teste puis déploie a besoin des deux comportements à la fois.
La solution est de les déclarer à des niveaux différents : le bloc du
workflow annule les runs obsolètes, celui du job deploy impose sa propre file
d'attente. Le groupe du job reste indépendant de celui du run qui le contient.
name: CI and Deploy
on: push: branches: [main]
# Concurrence globale pour ce workflowconcurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
# Aucun droit par défautpermissions: {}
jobs: test: runs-on: ubuntu-24.04 steps: - run: npm test
deploy: needs: test runs-on: ubuntu-24.04 # Concurrence spécifique pour le déploiement concurrency: group: deploy-production cancel-in-progress: false steps: - run: ./deploy.shProtection des runs sur main
Section intitulée « Protection des runs sur main »Ne pas annuler les runs sur la branche principale : sur main, chaque commit
mérite son propre résultat, ne serait-ce que pour savoir lequel a cassé la
build. cancel-in-progress accepte une expression, ce qui permet d'annuler
sur les branches de travail tout en laissant les runs de main aller au bout.
concurrency: group: ${{ github.workflow }}-${{ github.ref }} # Annuler uniquement sur les PRs cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}Comportement détaillé
Section intitulée « Comportement détaillé »La valeur de cancel-in-progress change radicalement ce qui arrive aux runs
déjà lancés. Comparons les deux modes.
Avec cancel-in-progress: true
Section intitulée « Avec cancel-in-progress: true »L'arrivée d'un nouveau run interrompt tous ceux du groupe, y compris ceux
qui étaient presque terminés. Les jobs annulés apparaissent avec le statut
cancelled et ne publient ni artefacts ni résultats de tests.
Workflow 1 (en cours) → ANNULÉWorkflow 2 (en cours) → ANNULÉWorkflow 3 (nouveau) → S'EXÉCUTEAvec cancel-in-progress: false
Section intitulée « Avec cancel-in-progress: false »Le run en cours va au bout quoi qu'il arrive, c'est précisément ce qu'on attend d'un déploiement ou d'une publication de paquet. Les runs suivants restent en attente sans mobiliser de runner tant que le groupe n'est pas libéré.
Workflow 1 (en cours) → SE TERMINEWorkflow 2 (en attente) → ATTENDWorkflow 3 (en attente) → ATTENDLes workflows en attente s'exécutent dans l'ordre.
Concurrence au niveau job
Section intitulée « Concurrence au niveau job »Vous pouvez aussi définir la concurrence au niveau d'un job spécifique : c'est le bon réglage quand un seul job du workflow touche un système partagé. Les autres jobs restent libres de tourner en parallèle, seul le job concerné est mis en file. Un job bloqué par son groupe attend sans consommer de runner, mais le run entier reste marqué en cours tant qu'il n'a pas démarré.
jobs: test: runs-on: ubuntu-24.04 # Pas de restriction de concurrence pour les tests steps: [...]
deploy: needs: test runs-on: ubuntu-24.04 concurrency: group: deploy-${{ github.ref }} cancel-in-progress: false steps: [...]Debugging
Section intitulée « Debugging »Les effets de concurrency se constatent après coup : un run disparaît, un
autre reste en attente sans explication apparente. Deux réflexes suffisent à
lever le doute, lire le motif d'annulation affiché par GitHub et faire
imprimer par le workflow les valeurs qui composent son groupe.
Voir les runs annulés
Section intitulée « Voir les runs annulés »Dans l'onglet Actions, les runs annulés affichent le statut "Cancelled" avec le message "This run was cancelled because a newer run was started".
Logs de concurrence
Section intitulée « Logs de concurrence »Le bloc concurrency n'apparaît nulle part dans les logs : pour savoir quel
groupe un run a réellement calculé, il faut afficher soi-même les valeurs de
contexte qui le composent. C'est le moyen le plus direct de repérer une
expression vide, cause classique d'annulations inattendues.
- name: Debug concurrency env: # Les valeurs de contexte passent par env:, jamais dans run: en clair WORKFLOW: ${{ github.workflow }} REF: ${{ github.ref }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} run: | echo "Workflow: $WORKFLOW" echo "Ref: $REF" echo "Run ID: $RUN_ID" echo "Run attempt: $RUN_ATTEMPT"Erreurs courantes
Section intitulée « Erreurs courantes »Trois réglages de groupe se retournent contre vous. Voici comment les reconnaître et les corriger.
Groupe trop large
Section intitulée « Groupe trop large »Un groupe constant met tous les workflows du dépôt en concurrence : le déclenchement d'une CI sur une branche annule la CI d'une autre branche, et les développeurs voient leurs runs disparaître sans raison visible.
# ❌ Tous les workflows sont dans le même groupe !concurrency: group: ci cancel-in-progress: true
# ✅ Groupe par workflow ET brancheconcurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: trueGroupe trop spécifique
Section intitulée « Groupe trop spécifique »github.run_id est unique par exécution : chaque run se retrouve donc seul dans
son groupe et n'entre jamais en concurrence avec un autre. Le bloc est bien
présent, il ne sert simplement à rien, ce qui est plus difficile à repérer
qu'une absence de configuration.
# ❌ Chaque run a son propre groupe (inutile)concurrency: group: ${{ github.run_id }}
# ✅ Groupe par brancheconcurrency: group: ${{ github.ref }}Annuler les déploiements
Section intitulée « Annuler les déploiements »C'est l'erreur la plus coûteuse : appliquer à un déploiement le réglage pensé
pour la CI. Un terraform apply ou un kubectl apply interrompu en cours
d'exécution laisse l'état partiellement appliqué, et rien ne le signale.
# ❌ Dangereux : peut annuler un déploiement en coursconcurrency: group: deploy cancel-in-progress: true
# ✅ Attendre le déploiement précédentconcurrency: group: deploy cancel-in-progress: falseContrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »concurrencyregroupe les runs et annule ou met en file ceux du même groupe.- Le groupe canonique est
${{ github.workflow }}-${{ github.ref }}: un groupe par workflow et par branche. cancel-in-progress: truepour la CI (économiser des minutes),falsepour les déploiements (ne jamais couper un déploiement en cours).- Un groupe trop large annule des runs sans rapport ; un groupe trop spécifique (
github.run_id) n'annule jamais rien. - Protégez
mainen conditionnant l'annulation :cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour la référence complète, consultez la documentation officielle sur la concurrency.