Par défaut, les jobs d'un workflow s'exécutent en parallèle et ne partagent
rien. Une vraie CI n'est pas une liste de jobs indépendants : c'est un graphe
où certaines étapes en attendent d'autres et consomment leurs résultats. Cette
page couvre needs, les outputs de job, et surtout ce qui se passe quand une
branche du graphe échoue.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Enchaîner des jobs avec
needs, en parallèle et en séquence - Faire circuler une valeur d'un job à l'autre avec les outputs
- Transporter une structure via JSON et
fromJSON - Maîtriser l'échec : jobs sautés,
always(),failure(),cancelled() - Combiner
needset matrices sans se piéger
Le parallélisme par défaut, et ce que needs change
Section intitulée « Le parallélisme par défaut, et ce que needs change »Sans needs, tous les jobs démarrent en même temps, chacun sur une machine
neuve. C'est rapide, et c'est faux dès qu'une étape dépend d'une autre :
publier avant d'avoir testé n'a pas de sens.
needs déclare une dépendance et, par voie de conséquence, un ordre :
name: CI
on: pull_request: branches: [main]
permissions: {}
jobs: lint: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: npm ci && npm run lint
test: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: npm ci && npm test
build: needs: [lint, test] runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: npm ci && npm run buildlint et test démarrent ensemble, build attend les deux. La forme du graphe
se lit dans les needs, pas dans l'ordre d'écriture des jobs : déplacer build
en tête du fichier ne change rien.
lint ───┐ ├──> build ──> scan ──> publishtest ───┘Cette structure, plusieurs analyses en parallèle puis une convergence, est la forme la plus courante d'une CI sérieuse. Elle donne le retour le plus rapide possible sur les erreurs les plus fréquentes, tout en garantissant que rien n'est construit sur du code qui ne passe pas.
Faire circuler une valeur entre jobs
Section intitulée « Faire circuler une valeur entre jobs »Chaque job tourne sur une machine différente. Rien ne se transmet automatiquement : ni fichiers, ni variables. Pour les fichiers, ce sont les artefacts ; pour les valeurs, ce sont les outputs.
Le mécanisme se déclare à deux niveaux. L'étape écrit dans $GITHUB_OUTPUT, le
job promeut cette valeur en output de job :
jobs: version: runs-on: ubuntu-24.04 permissions: contents: read outputs: numero: ${{ steps.calcul.outputs.numero }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Calculer le numéro de version id: calcul run: echo "numero=1.4.$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
build: needs: version runs-on: ubuntu-24.04 permissions: contents: read env: VERSION: ${{ needs.version.outputs.numero }} steps: - name: Construire avec la version calculée run: echo "Construction de la version $VERSION"Trois règles évitent l'essentiel des erreurs :
- L'
idde l'étape est obligatoire pour être référencée. Sansid:, la valeur est inaccessible. - Le job consommateur doit déclarer
needs. Le contexteneeds.<job>.outputsn'existe que pour les jobs dont on dépend, même si le job producteur a déjà terminé. - Tout output est une chaîne de caractères. Il n'y a ni entier, ni booléen, ni tableau.
Transporter une structure avec JSON
Section intitulée « Transporter une structure avec JSON »Puisqu'un output est une chaîne, transporter une liste ou un objet passe par du
JSON sérialisé, décodé à l'arrivée par fromJSON.
jobs: cibles: runs-on: ubuntu-24.04 permissions: contents: read outputs: liste: ${{ steps.detect.outputs.liste }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Détecter les services modifiés id: detect run: echo 'liste=["api","worker","frontend"]' >> "$GITHUB_OUTPUT"
deployer: needs: cibles runs-on: ubuntu-24.04 permissions: contents: read strategy: matrix: service: ${{ fromJSON(needs.cibles.outputs.liste) }} steps: - name: Déployer un service env: SERVICE: ${{ matrix.service }} run: echo "Déploiement de $SERVICE"C'est le motif de la matrice dynamique : un premier job calcule la liste des cibles, un second en dérive autant d'exécutions parallèles. Il évite de maintenir à la main une matrice qui répète ce que le dépôt contient déjà.
Une limite à connaître : la chaîne JSON doit être valide et sur une seule
ligne. Un JSON produit par un outil peut contenir des retours à la ligne qui
cassent l'écriture dans $GITHUB_OUTPUT. Le compacter règle le problème :
echo "liste=$(jq -c . cibles.json)" >> "$GITHUB_OUTPUT"Ce qui se passe quand un job échoue
Section intitulée « Ce qui se passe quand un job échoue »C'est la partie qui surprend, et elle tient en une phrase de la documentation GitHub :
Si un job échoue ou est sauté, tous les jobs qui en dépendent sont sautés, à moins que ces jobs n'utilisent une expression conditionnelle qui les fasse continuer.
Deux conséquences pratiques. Un job dépendant n'est pas marqué en échec, il est sauté : le workflow affiche un résultat qui n'est pas rouge partout, ce qui trompe à la lecture rapide. Et un job sauté propage : toute sa descendance est sautée à son tour.
Forcer l'exécution malgré l'échec
Section intitulée « Forcer l'exécution malgré l'échec »La documentation est explicite sur le moyen :
Si vous voulez qu'un job s'exécute même lorsqu'un job dont il dépend n'a pas réussi, utilisez l'expression conditionnelle
always().
rapport: needs: [lint, test, build] if: always() runs-on: ubuntu-24.04 permissions: contents: read steps: - name: Publier le rapport quel que soit le résultat env: RESULTAT_TEST: ${{ needs.test.result }} run: echo "Résultat des tests : $RESULTAT_TEST"Le contexte needs.<job>.result porte l'issue réelle du job dont on dépend :
success, failure, cancelled ou skipped. C'est lui qui permet à un job de
rapport de dire ce qui a échoué.
Quatre fonctions couvrent les cas courants :
| Condition | Le job s'exécute |
|---|---|
success() | Si tous les jobs de needs ont réussi, c'est le défaut implicite |
failure() | Si au moins un job de needs a échoué |
cancelled() | Si le workflow a été annulé |
always() | Systématiquement, y compris après annulation |
Le cas de continue-on-error
Section intitulée « Le cas de continue-on-error »Un job marqué continue-on-error: true est considéré comme réussi du point
de vue de ses dépendants, même s'il a échoué. C'est utile pour un contrôle
informatif, et dangereux pour un contrôle de sécurité : un scanner en
continue-on-error ne bloque plus rien, il décore.
Si vous voulez conserver l'information sans bloquer, gardez le job bloquant et
laissez le job suivant décider à partir de needs.<job>.result.
Combiner needs et matrices
Section intitulée « Combiner needs et matrices »Un job qui dépend d'un job matriciel attend toutes les combinaisons, pas la première. C'est le comportement souhaitable : on ne construit pas une image parce que les tests passent sur une seule version de Python.
jobs: test: runs-on: ubuntu-24.04 permissions: contents: read strategy: fail-fast: false matrix: python: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ matrix.python }} - run: pytest -q
build: needs: test runs-on: ubuntu-24.04 permissions: contents: read steps: - run: echo "Toutes les versions ont passé les tests"Deux points de vigilance. fail-fast: false laisse toutes les branches
aller au bout : sans lui, le premier échec annule les autres et vous perdez
l'information la plus utile, à savoir si le problème touche une version ou
toutes. Et les outputs d'un job matriciel sont ambigus : toutes les
combinaisons écrivent dans le même output, la dernière terminée l'emporte. Ne
comptez pas dessus, utilisez des artefacts nommés par combinaison.
Relire son graphe
Section intitulée « Relire son graphe »Une fois le pipeline écrit, trois questions suffisent à repérer les défauts structurels courants.
-
Quels jobs pourraient tourner en parallèle mais ne le font pas ? Un
needsde confort allonge le pipeline sans rien garantir. -
Quel job publie ou déploie, et de quoi dépend-il réellement ? Si sa chaîne de
needsne remonte pas jusqu'aux tests et aux scanners de sécurité, il peut livrer du code refusé ailleurs. -
Quel job porte un
always()ou uncontinue-on-error? Chacun est une exception à justifier, et c'est là que les contrôles s'évaporent le plus discrètement.
Contrô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 »- Les jobs sont parallèles par défaut ;
needsdéclare une dépendance et dessine le graphe, indépendamment de l'ordre d'écriture. - La forme utile est fan-out puis convergence : analyses en parallèle, puis build, puis publication.
- Une valeur circule par les outputs : l'étape écrit dans
$GITHUB_OUTPUT, le job la promeut, le consommateur déclareneeds. - Tout output est une chaîne : les structures passent par du JSON compacté et
fromJSON, motif de la matrice dynamique. - Ne faites pas transiter un secret par un output : le job qui en a besoin le lit directement.
- Un job dont une dépendance échoue est sauté, pas en échec, et il propage ce statut à sa descendance.
always()s'exécute même après annulation : acceptable pour un rapport, jamais pour un job qui publie ou déploie ; préférez!cancelled().continue-on-errorfait passer un job pour réussi auprès de ses dépendants : à proscrire sur un contrôle de sécurité.- Un job qui dépend d'une matrice attend toutes les combinaisons ; ses outputs sont ambigus, préférez des artefacts.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Workflows réutilisables : Factoriser un graphe qui se répète d'un dépôt à l'autre, avec ses propres entrées et sorties.
- Actions composites : Regrouper une suite d'étapes récurrentes, là où le workflow réutilisable serait surdimensionné.
- Sécuriser GitHub Actions : Ce que le graphe de dépendances change pour la sécurité, à commencer par les jobs qui publient.