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 »| 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 »# 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.
# 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 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 »# Un seul déploiement par environnementconcurrency: group: deploy-${{ github.event.inputs.environment || 'staging' }} cancel-in-progress: falsePatterns courants
Section intitulée « Patterns courants »CI classique
Section intitulée « CI classique »Annuler les runs obsolètes pour économiser les ressources :
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 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é :
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - run: ./deploy.shDifférencier CI et déploiement
Section intitulée « Différencier CI et déploiement »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 :
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 »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 »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 :
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 »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 »- 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 »# ❌ 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 »# ❌ 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 »# ❌ 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: falseÀ 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.