setup-node

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

5posts · 30d
Nov 30 posts
  1. GitHub Actions : le tutoriel pas à pas pour tester et publier une API Node.js Les six notions de GitHub Actions Tout workflow GitHub Actions se lit avec six mots, définis dans la page Understanding GitHub Actions de la documentation officielle (consultée le 31 octobre 2026) : | Notion | Ce que c'est | Dans notre exemple | |---|---|---| | Workflow | Un processus automatisé décrit par un fichier YAML dans .github/workflows/ | ci.yml | | Événement | L'activité du dépôt qui déclenche le workflow | push et pull_request sur main | | Job | Une suite d'étapes exécutées sur un même runner ; les jobs tournent en parallèle sauf dépendance | test et image | | Step | Une commande shell ou une action, exécutées dans l'ordre | npm ci, npm test | | Runner | La machine qui exécute un job, un seul job à la fois | ubuntu-latest, hébergé par GitHub | | Action | Une extension réutilisable, souvent prise sur le GitHub Marketplace | actions/checkout, docker/login-action | Chaque exécution part d'une machine virtuelle neuve, et chaque action du Marketplace est du code tiers qui tourne avec vos droits, d'où l'étape 6. !Schéma du workflow GitHub Actions de ce tutoriel : un événement push ou pull_request déclenche deux jobs de test en parallèle sur Node.js 24 et 26, puis un job qui publie l'image Docker sur ghcr.io --- Étape 1 : un premier workflow pour l'API Node.js Le point de départ : récupérer le code, installer Node.js et les dépendances, lancer les tests. L'API du tutoriel déclare "test": "node --test" dans son package.json et exige Node.js 24 ou plus. Créez .github/workflows/ci.yml : name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: 24 - run: npm ci - run: npm test npm ci installe exactement ce que fixe package-lock.json, que le dépôt doit contenir, et npm test lance node --test. Le timeout-minutes évite qu'un test bloqué tourne jusqu'à la limite de 6 heures par job des runners hébergés, fixée par la page des limites de GitHub Actions. Pourquoi checkout v7 et pas v4 ? Selon son README, la v5 est passée au moteur node24, la v6 range les identifiants Git hors de .git/config, et la v7 refuse par défaut le code d'un fork sous pull_request_target ou workflow_run. Les dernières versions au 31 octobre 2026 sont actions/checkout v7.0.1 et actions/setup-node v7.1.0. L'onglet Actions affiche ensuite chaque exécution ; pour lire les journaux d'un runner Ubuntu, voyez nos commandes Linux de base. --- Étape 2 : tester sur Node.js 24 et 26 avec une matrice Une matrice exécute le même job plusieurs fois avec des valeurs différentes. Pour l'API, deux versions comptent : Node.js 24, minimum exigé par le package.json, et Node.js 26, passé en LTS avec la version 26.13.0 du 28 octobre 2026 selon l'index des versions de Node.js. Remplacez le job test : test: runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: node-version: [24, 26] steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test Les deux jobs tournent en parallèle. fail-fast: false laisse finir le second si le premier échoue. La page des limites plafonne une matrice à 256 jobs, et chaque job est facturé séparément. --- Étape 3 : le cache, plus rapide mais pas sans risque Le cache de GitHub Actions évite de retélécharger les paquets npm à chaque exécution : ajoutez cache: npm sous with. D'après le README de setup-node, la clé dérive du hachage de package-lock.json et l'action met en cache le dossier global de npm, pas node_modules. - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: npm Les limites, lues dans la référence du cache de dépendances le 31 octobre 2026 : - 10 Go par dépôt par défaut. Une entrée non lue depuis plus de 7 jours est supprimée. - Aucun secret dans le cache. Toute personne ayant un accès en lecture peut ouvrir une pull request et lire un cache. Le mot-clé cache-mode (read, write, write-only ou none) fixe les droits d'un job sur le cache. Il se place en tête du workflow ou dans un job, par exemple cache-mode: read sous test:, d'après la référence de syntaxe. Selon la documentation du cache consultée le 31 octobre 2026, un déclencheur peu fiable comme pull_request_target n'obtient par défaut que la lecture, et y déclarer write réintroduit le risque d'empoisonnement du cache. --- Étape 4 : secrets, variables et environnements Un jeton n'entre jamais dans le YAML : il se range dans Settings, Secrets and variables, Actions, et se lit avec ${{ secrets.NOM }}. Une valeur non sensible, comme une URL, devient une variable lue avec ${{ vars.NOM }}, au niveau du dépôt, de l'organisation ou d'un environnement, selon la page sur les variables. Pour rattacher le job image de l'étape 5 à un environnement production, avec une variable PRODUCTION_URL créée dans cet environnement, ajoutez au job : environment: name: production url: ${{ vars.PRODUCTION_URL }} La référence de syntaxe autorise le contexte vars dans url. Mais la page de gestion des environnements réserve les environnements aux dépôts publics sur GitHub Free ; en privé, il faut Pro, Team ou Enterprise. C'est pourquoi le fichier complet plus bas s'en passe. Notre workflow n'a besoin d'aucun secret ajouté à la main. Les tests de l'API fixent eux-mêmes JWT_SECRET et DB_PATH=':memory:' dans test/api.test.js, et le job image construit l'image sans démarrer le conteneur. Quant au GITHUB_TOKEN, créé pour chaque job, il suffit à publier sur le registre de GitHub. --- Étape 5 : construire et publier l'image Docker sur GHCR GHCR, le registre de conteneurs de GitHub (ghcr.io), accepte un push authentifié par GITHUB_TOKEN. L'image reprend le Dockerfile multi-étapes de notre tutoriel Docker pour débutants, basé sur node:24-alpine, placé à la racine du projet. Ajoutez ce job à ci.yml : image: needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read packages: write steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: docker/setup-buildx-action@v4 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@v6 with: images: ghcr.io/${{ github.repository }} tags: | type=ref,event=branch type=sha - uses: docker/build-push-action@v7 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max Ce que fait chaque bloc : - needs et if. L'image n'est construite que si les deux tests réussissent, et seulement pour un push sur main. - docker/metadata-action. L'image prend le nom du dépôt, ghcr.io/<compte>/<dépôt>, soit ghcr.io/<compte>/api-taches si le dépôt s'appelle comme le dossier du tutoriel. L'action produit les tags main et sha- suivi du commit abrégé, plus les labels OCI. Son README précise qu'il met le nom d'image en minuscules, ce qu'exige le registre. - context: . avec checkout. Sans checkout, build-push-action lit le dépôt par Git et ignore les modifications des étapes précédentes, selon son README. - type=gha. Les couches Docker vont dans le cache de GitHub Actions. Avec plusieurs images, fixez un scope distinct, sinon chaque build écrase le cache du précédent, prévient la documentation de Docker. Deux pièges documentés par GitHub sur la page du Container registry. Un paquet publié pour la première fois est privé par défaut. Et le GITHUB_TOKEN ne peut pas pousser si une image du même nom a déjà été poussée à la main sans être reliée au dépôt. Côté coût, la facturation de GitHub Packages indique le 31 octobre 2026 que le stockage du Container registry est gratuit pour l'instant, avec un mois de préavis avant tout changement. L'image se déploie ensuite derrière notre reverse proxy Nginx avec Docker Compose. --- Étape 6 : GITHUB_TOKEN minimal et actions épinglées Cette étape a deux volets : limiter ce que le jeton peut faire, et figer le code des actions exécutées. Le principe du moindre privilège Le GITHUB_TOKEN est limité au dépôt et valable le temps du job. Ses droits dépendent du réglage du dépôt, puis du bloc permissions. La référence de syntaxe fixe la règle utile : dès que vous citez une permission, toutes celles que vous ne citez pas passent à none. D'où notre schéma : contents: read au niveau du workflow, packages: write dans le seul job image. C'est ce que récompense le test Token-Permissions d'OpenSSF Scorecard, qui classe ce risque comme élevé. L'option persist-credentials: false de checkout évite en plus de conserver le jeton pour les commandes Git suivantes. Épingler les actions sur un SHA Un tag comme @v7 peut être déplacé. Selon l'avis de sécurité GitHub GHSA-mrrh-fwg8-r2c3 (CVE-2025-30066), des attaquants ont redirigé en mars 2025 plusieurs tags de tj-actions/changed-files vers un commit malveillant, qui affichait les secrets dans les journaux entre le 14 et le 15 mars. La référence de sécurité de GitHub Actions recommande donc d'épingler chaque action sur l'empreinte complète d'un commit, en vérifiant qu'elle vient du dépôt de l'action et non d'un fork. La forme est uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 : l'empreinte fige le code, le commentaire garde la version lisible. Le fichier complet plus bas épingle ainsi les six actions sur les versions publiées au 31 octobre 2026. Nous avons lu ces empreintes sur l'API de GitHub ; vérifiez-les sur la page des versions de chaque action. Pour ne pas figer des versions vulnérables, laissez Dependabot proposer les mises à jour avec .github/dependabot.yml, selon la documentation de Dependabot : version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" Selon la référence de sécurité, les alertes Dependabot ignorent les actions épinglées par SHA ; les mises à jour de version, elles, fonctionnent. GitHub va plus loin avec les fichiers de verrouillage des workflows : .github/workflows/actions.lock, généré par l'extension gh actions-lock, fige le commit de chaque action, dépendances indirectes comprises, et vérifie l'identité du dépôt source. La référence de sécurité le présente comme la meilleure protection, qui remplace l'épinglage par SHA pour les dépôts qui l'adoptent. Ce tutoriel garde les SHA : la documentation ne dit pas si le dispositif est en préversion ou disponible pour tous, et il exige une extension de GitHub CLI. --- Étape 7 : concurrence et workflows réutilisables Deux mots-clés évitent le gaspillage et la copie de YAML. Le premier, concurrency, se place en tête de ci.yml : concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} D'après la page sur la concurrence, un seul workflow s'exécute à la fois par groupe, et une exécution en attente est remplacée par la plus récente. L'expression annule aussi l'exécution en cours, mais seulement pour une pull request. Sur main, l'exécution en cours va à son terme, mais une exécution en attente reste remplacée par la plus récente : seul le dernier push en file est publié. Le second, workflow_call, transforme un workflow en brique réutilisable. Déplacez le job de test dans .github/workflows/tests-node.yml : name: Tests Node.js on: workflow_call: inputs: node-versions: description: Versions de Node.js, au format JSON type: string default: '[24, 26]' jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read strategy: fail-fast: false matrix: node-version: ${{ fromJSON(inputs.node-versions) }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci - run: npm test Dans ci.yml, le job test devient un simple appel, et image garde son needs: test : jobs: test: uses: ./.github/workflows/tests-node.yml permissions: contents: read with: node-versions: '[24, 26]' La forme ./.github/workflows/{filename} est celle que la documentation sur la réutilisation donne pour un workflow du même dépôt. La même page, consultée le 31 octobre 2026, mentionne aussi une forme $/.github/workflows/{filename}, qu'elle dit recommandée et indisponible sur GitHub Enterprise Server ; nous ne l'avons pas testée. La référence des workflows réutilisables fixe dix niveaux d'imbrication au plus et 50 workflows réutilisables distincts par fichier ; les variables env de l'appelant ne sont pas transmises, et le workflow appelé ne peut pas élever les permissions reçues. --- Le fichier ci.yml complet Voici .github/workflows/ci.yml une fois les étapes 1 à 7 assemblées, avec le job test écrit en entier (sans le workflow réutilisable de l'étape 7 ni l'environnement optionnel de l'étape 4) : déclencheurs, permissions minimales, concurrence, matrice avec cache, publication sur GHCR, et chaque action épinglée sur son SHA. Sa syntaxe a été vérifiée avec le module yaml de Python ; il n'a pas été exécuté sur GitHub. Avec le workflow réutilisable de l'étape 7, le job test ci-dessous est remplacé par l'appel à tests-node.yml. name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: node-version: [24, 26] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci - run: npm test image: needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read packages: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: ghcr.io/${{ github.repository }} tags: | type=ref,event=branch type=sha - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max --- Combien coûte GitHub Actions en octobre 2026 GitHub Actions reste gratuit pour les dépôts publics sur les runners standards, et sur vos propres machines. Pour les dépôts privés, la page de facturation de GitHub Actions, lue le 31 octobre 2026, exprime désormais le quota en dollars de calcul inclus, et non plus en minutes, déduits selon le tarif de chaque runner : | Offre | Calcul inclus par mois | Soit, sur Linux 2 cœurs | Stockage d'artefacts | Cache par dépôt | |---|---|---|---|---| | GitHub Free | 12 $ | 2 000 minutes | 500 Mo | 10 Go | | GitHub Pro | 18 $ | 3 000 minutes | 1 Go | 10 Go | | GitHub Team | 18 $ | 3 000 minutes | 2 Go | 10 Go | | GitHub Enterprise Cloud | 300 $ | 50 000 minutes | 50 Go | 10 Go | La conversion est notre calcul à 0,006 $ la minute. Les 2 000 minutes souvent citées, y compris dans notre guide CI/CD, correspondent donc aux 12 $ de l'offre Free sur Linux 2 cœurs ; la page de prix de GitHub affiche bien 2 000, 3 000 et 50 000 minutes pour Free, Team et Enterprise, avec Team à 4 $ et Enterprise à 21 $ par utilisateur et par mois. Le quota en dollars change la donne dès que vous quittez Linux. La grille des runners facture 0,002 $ la minute sur Linux 1 cœur, 0,005 $ sur Linux arm64 2 cœurs, 0,010 $ sur Windows 2 cœurs et 0,062 $ sur macOS 3 ou 4 cœurs. Les 12 $ de l'offre Free couvrent donc environ 193 minutes de macOS seulement. !Graphique en barres horizontales GitHub Actions : selon notre calcul, les 12 dollars inclus dans GitHub Free couvrent 6 000 minutes sur Linux 1 cœur, 2 400 sur Linux arm64 2 cœurs, 2 000 sur Linux 2 cœurs, 1 200 sur Windows 2 cœurs et 193 sur macOS 3 ou 4 cœurs > Notre calcul. Chaque job étant arrondi à la minute supérieure, notre ci.yml coûte au moins 3 minutes par push sur main et 2 par pull request. À 50 de chaque par mois sur un dépôt privé, cela fait au moins 250 minutes, bien dans le quota Free. Une matrice sur trois systèmes d'exploitation changerait tout : une minute de macOS coûte plus de dix minutes de Linux 2 cœurs. Le stockage se paie à part : 0,25 $ par Go et par mois au-delà de l'allocation d'artefacts, 0,07 $ pour le cache si vous avez relevé sa limite de 10 Go, selon la même page. --- Runners auto-hébergés : gratuits, pas sans danger Un runner auto-hébergé est une machine à vous qui exécute les jobs à la place des machines de GitHub. La page de facturation les range, le 31 octobre 2026, parmi les usages gratuits qui ne réduisent pas le calcul inclus. GitHub avait annoncé en décembre 2025 une redevance de 0,002 $ la minute sur les runners auto-hébergés à partir du 1er mars 2026, puis a écrit la reporter « pour prendre le temps de réévaluer notre approche » (notre traduction), sur sa page consacrée aux changements de prix de 2026. Selon la page sur les runners auto-hébergés, vous payez la machine et mettez à jour vous-même système et outils. Checkout et setup-node, passées au moteur node24, exigent le runner v2.327.1 ou plus selon leurs README. Un job peut y tourner 5 jours d'après la page des limites, mais le GITHUB_TOKEN ne se renouvelle que pendant 24 heures, précise la page GITHUB_TOKEN. Le vrai risque est ailleurs. Selon la référence de sécurité, un runner auto-hébergé ne garantit pas une machine propre à chaque job et peut être compromis durablement : il ne devrait presque jamais servir à un dépôt public, où n'importe qui peut ouvrir une pull request. Elle mentionne aussi des runners éphémères, dits JIT, qui n'exécutent qu'un job. Verdict : pour cette API, les runners hébergés suffisent. Notre comparatif de l'hébergement cloud pour développeurs aide à choisir la machine. --- Ce que nous n'avons pas pu vérifier Ce tutoriel est une synthèse documentaire. Restent incertains : - L'exécution des workflows. Leur syntaxe YAML a été vérifiée avec un analyseur Python, mais aucun n'a tourné sur GitHub. Testez-les d'abord sur une branche. - Le sort de la redevance des runners auto-hébergés. Reportée, mais nous n'avons trouvé aucune annonce qui l'annule. - Le prix de GitHub Pro. La page de prix lue ne l'affiche pas, ni aucun prix en euros. - Le statut exact des fichiers de verrouillage. La documentation les décrit sans préciser s'ils sont en préversion ou disponibles pour tous. - La durée réelle des jobs. Nos chiffres de minutes sont des minimums dus à l'arrondi, pas des mesures. --- Questions fréquentes Combien de minutes GitHub Actions gratuites pour un dépôt privé ? Sur l'offre Free, 12 $ de calcul par mois, soit 2 000 minutes sur le runner Linux 2 cœurs à 0,006 $ la minute, mais 193 seulement sur macOS, selon la page de facturation de GitHub lue le 31 octobre 2026. Les dépôts publics sur les runners standards et les runners auto-hébergés ne consomment rien. Quelle version d'actions/checkout utiliser ? La v7 : au 31 octobre 2026, la dernière version publiée est la 7.0.1. Épinglez-la sur son SHA complet, avec la version en commentaire. Quelle différence entre une action et un workflow ? Le workflow est votre fichier YAML, déclenché par un événement, qui contient des jobs. Une action est une brique appelée dans un step avec uses, comme actions/setup-node. Un workflow réutilisable s'appelle, lui, au niveau d'un job. Comment publier une image Docker avec GitHub Actions ? Connectez-vous à ghcr.io avec docker/login-action et le GITHUB_TOKEN, puis construisez et poussez avec docker/build-push-action, dans un job qui déclare packages: write (étape 5). --- À propos de l'auteur Abdelilah Ourti est ingénieur IA et architecte logiciel. Il conçoit et déploie des applications web et des systèmes d'IA, et publie sur NeurauTech des guides et des comparatifs destinés aux développeurs francophones. Ses autres articles sont réunis sur sa page auteur. Méthode. Synthèse documentaire fondée exclusivement sur des sources publiques consultées le 31 octobre 2026 : documentation de GitHub Actions, de GitHub Packages et de Dependabot, pages de facturation et de prix de GitHub, README et pages de versions des actions officielles et des actions Docker, documentation de Docker, OpenSSF Scorecard, avis de sécurité de GitHub, index des versions de Node.js. Les empreintes SHA ont été lues sur l'API de GitHub. La syntaxe YAML a été vérifiée localement, mais aucun workflow n'a été exécuté. Aucun lien n'est affilié et aucune relation commerciale ne lie l'auteur aux éditeurs mentionnés. Une erreur ou une version périmée ? Écrivez à contact@neurautech.com : l'article sera corrigé et la date de mise à jour modifiée. Dernière mise à jour : 31 octobre 2026. Image : Alberto Rodríguez (Unsplash License).

  2. GitHub Actions Node 20 Deprecation: What Broke on 23 September (and Why node20 Isn't the Problem) GitHub removed Node 20 from the Actions runners on 23 September 2026, and an action that declares using: node20 still doesn't fail for that: since 16 June the runner rewrites node20 to node24 before it executes anything, and it does the same with node12 and node16. What went away on the 23rd was the escape hatch: the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION variable no longer brings Node 20 back. The next date with real consequences is 2 November, when macos-14 is retired. That inverts who is at risk. If your CI has been green since June without that variable, the 23rd changed nothing for you on GitHub-hosted runners. The ones that break are precisely the teams that already hit an action that can't handle Node 24, set the variable to keep moving, and filed the real fix under later. And around that date there are four more with real consequences: the self-hosted runner minimum version (25 September), retention for checks and runs (1 October), the macos-14 brownouts (October) and its retirement (2 November). I checked this against the runner's source code and against this site's own CI, which uses two node20 actions. Everything below comes with its source. The calendar, by date | Date | What changes | What breaks | How to detect it | |---|---|---|---| | 14, 16 and 18 Sep (11:00-15:00 ET) | Brownouts ahead of the self-hosted runner minimum version | Outdated runners cannot register or run jobs during the window | Runner deprecations API (below) | | 17 Sep | ubuntu-22.04 and ubuntu-22.04-arm enter deprecation | Nothing fails yet; queues may be longer | grep for the label | | 21-30 Sep | windows-11-arm moves to the Visual Studio 2026 image | The toolchain underneath changes | grep windows-11-arm | | 23 Sep | Node 20 leaves the runners | ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION is ignored; Linux ARM32 runners lose support | grep for the variable | | 25 Sep | Self-hosted runner minimum version is enforced | Registering requires ≥ 2.329.0; a runner that doesn't install each release within 30 days stops receiving jobs | Deprecations API | | 1 Oct | Actions retention now covers checks, workflow runs and statuses | Anything past your retention period (90 days by default) is deleted | Repo/org retention setting | | 5-31 Oct | Eight macos-14 brownouts | Jobs scheduled inside the window fail | grep macos-14 | | 2 Nov | macos-14 retired | Jobs with that label terminate with an error | grep macos-14 | | 17 Apr 2027 | ubuntu-22.04 retired (brownouts before, in March and April) | Jobs terminate with an error | grep for the label | 1. Node 20: what actually happens on 23 September What the notice says GitHub's changelog post from 19 September 2025, with an editor's note dated 25 August 2026, sets two dates: from 16 June 2026 runners use Node 24 by default, and the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true opt-out back to Node 20 "will only work until we upgrade the runner and remove Node20 on September 23rd, 2026." It adds two platform limits: Node 24 is incompatible with macOS 13.4 and lower, and it has no official ARM32 support. What the notice does not explain is what happens to an action whose action.yml still says using: node20. That lives in the code. What the runner code says In HandlerFactory.cs (identical at tag v2.337.0, the current release), the runner does two things before launching a JavaScript action: 1. If the action declares node12 or node16, it bumps it to node20. 2. If it ends up on node20, it asks NodeUtil.DetermineActionsNodeVersion which version to use. That function has three phases, driven by feature flags on GitHub's side: // Phase 3: Always use Node 24 regardless of environment variables if (requireNode24) { return (Constants.Runner.NodeMigration.Node24, null); } // ... // Phase 2: Node 24 is the default if (useNode24ByDefault) { if (allowUnsecureNode) { return (Constants.Runner.NodeMigration.Node20, null); } return (Constants.Runner.NodeMigration.Node24, null); } PR #3948, which introduced this, is blunt about it: in phase 3, "All actions use Node 24" and "No opt-out options available." No branch returns an error for declaring node20. The action runs on a different Node, which is very different from not running. Note the order too: node12 → node20 → node24. An old action with using: node16 (for example actions/checkout@v3) ends up on Node 24 as well. The proof: this site's CI This blog's CI workflow uses actions/checkout@v4 and actions/setup-node@v4. The official action.yml files at those tags declare using: node20; from v5 onward they declare node24 (I checked tags v4 through v7 of both). The run on 11 September, on runner 2.337.0 with the ubuntu-24.04 image, finished green with this annotation: Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4, actions/setup-node@v4. And in the log of those two actions' steps, this line: Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. The same annotation appears on the very first run of that workflow, on 23 August. In other words, my two node20 actions have never run on Node 20 in this CI, and I didn't touch anything to make that happen. 23 September doesn't change their runtime, because that change already happened in June. Bumping them to v5 or later clears the annotation, and it's worth doing, but there's no clock on it. What it looks like when it does break There are three real ways to fail, and none of them is a GitHub message saying "Node 20 no longer exists." You had the variable set. Some action of yours tripped on Node 24 after 16 June, you set ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true, and it stayed on Node 20. In phase 3 the runner ignores the variable without telling you: the requireNode24 branch returns null as its message. The action goes back to Node 24, and the error you see is its own code's error, the same one you saw the first time. One hint that phase 3 is live: with the current runner code, the "If you need to temporarily use Node 20…" line disappears from the log, because it is only printed while Node 24 is the default but not yet mandatory. The action uses something Node 24 removed. Per the Node 24 deprecations table and the 24.0.0 release notes, these reached end-of-life in that release: | API | Status in Node 24 | How it fails | |---|---|---| | tls.createSecurePair() | Removed (DEP0064) | TypeError: ... is not a function when called | | fs.truncate() with a file descriptor | Removed (DEP0081) | Throws ERR_INVALID_ARG_TYPE: it expects a path. Use fs.ftruncate() | | dirent.path | Removed (DEP0178) | Doesn't throw on its own: the property is gone and you read undefined. Use dirent.parentPath | | OutgoingMessage.prototype._headers / _headerNames | Removed (DEP0066) | You read undefined. Use getHeaders() | dirent.path is the sneakiest one on the list, because reading it throws nothing: the failure shows up later, wherever that path gets used, or never, if it's only concatenated into a string and comes out as undefined/file. If you maintain an action that walks directories, search for that property before you trust the green check. Your self-hosted runner can't run Node 24. On Linux ARM32, the same NodeUtil.cs has a kill switch that fails the step with: Linux ARM32 runners are no longer supported. Please migrate to a supported platform. Before it's flipped, the runner keeps those actions on Node 20 with a warning whose default date is the Node 20 removal date: "Linux ARM32 runners are deprecated and will no longer be supported after September 23rd, 2026." GitHub hasn't published the exact day the switch flips; the changelog only says ARM32 loses support after the Node 20 deprecation. On macOS, Node 24's BUILDING.md requires macOS 13.5 or later. 2. The audit: what to look for and where In one repository Run this from the root: # 1. The switch that stops working on 23 September grep -rn 'ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION' .github/ # 2. Image labels with a date. No "runs-on:" on purpose, # so matrices show up too (os: [ubuntu-22.04, ...]) grep -rnE 'ubuntu-22\.04|macos-14|windows-11-arm' .github/ # 3. Your own or local actions that declare an old Node grep -rnE --include=action.yml --include=action.yaml \ "using:[[:space:]]*['\"]?node(12|16|20)" . Search 2 has a blind spot: runs-on: ${{ vars.RUNNER }} or a label passed as an input to a reusable workflow won't show up in any grep. For those, the log is the source of truth. The "Set up job" step prints the real image: gh run view <run-id> --log | grep -m1 'Image:' # Image: ubuntu-24.04 If the matrix is long or nested, converting it to JSON and filtering it with jq is usually faster than reading the YAML by eye: > Free tool: JSON and YAML Converter — Convert JSON to YAML and YAML to JSON instantly, with configurable indentation, download and copy. All in your browser, nothing uploaded. To find out which runtime each third-party action you use declares, including SHA-pinned ones, you have to read its action.yml at that exact ref. This script does it with gh: grep -rhoE "uses:[[:space:]]*['\"]?[^[:space:]'\"#]+@[^[:space:]'\"#]+" .github/ \ | sed -E "s/uses:[[:space:]]*['\"]?//" | sort -u \ | while read -r ref; do spec=${ref%@*}; ver=${ref#*@} case "$spec" in ./*|docker://*) continue ;; esac repo=$(cut -d/ -f1-2 <<<"$spec"); sub=$(cut -d/ -f3- <<<"$spec") for f in action.yml action.yaml; do rt=$(gh api "repos/$repo/contents/${sub:+$sub/}$f?ref=$ver" --jq .content 2>/dev/null \ | base64 -d | grep -E '^[[:space:]]*using:' | tr -d " '\"" | cut -d: -f2) [ -n "$rt" ] && { echo "$rt $ref"; break; } done done On this repo it prints: node20 actions/checkout@v4 node20 actions/setup-node@v4 It handles subpaths (github/codeql-action/init@v3) and SHAs. A SHA pointing at a node20 release doesn't fail on the 23rd, for the same reason @v4 doesn't: the runner forces it onto Node 24. What's different with a SHA is how it gets updated: nothing moves on its own, so Dependabot or a human has to bump it. One warning before bumping a major: read the release notes. actions/setup-node v5 turned on automatic caching when package.json has a packageManager field, and v6 limited it to npm. If you already pass cache: npm explicitly, as my CI does, it doesn't affect you. Across an organization GitHub code search reaches every repo at once: gh search code --owner YOUR_ORG 'ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION' gh search code --owner YOUR_ORG 'ubuntu-22.04 path:.github/workflows' gh search code --owner YOUR_ORG 'macos-14 path:.github/workflows' gh search code --owner YOUR_ORG node20 --filename action.yml Two limits of the code search API that will bite you: it only searches the default branch (a workflow on a release branch won't show up) and it allows 10 requests per minute. While preparing this post I hit HTTP 403: API rate limit exceeded as soon as I chained a handful of searches; add a sleep between them if you automate it. 3. Self-hosted runners: two dates in three days If you only use GitHub-hosted runners, skip this section. If you run your own, 25 September matters more than the 23rd. The 12 June changelog post resumes enforcement of two rules on github.com: - To register (or re-register) a runner you need version 2.329.0 or later. - To keep running jobs, the runner must install each new release within 30 days of its publication. "A runner pinned to 2.329.0 that never updates again will not pick up jobs." Full enforcement begins on 25 September 2026 (the notice's table labels that date as GitHub Enterprise Cloud, and the same notice states the change applies to github.com), with brownouts first: on 14, 16 and 18 September, from 11:00 AM to 3:00 PM Eastern, out-of-date runners can neither register nor run jobs. What you see, per the notice, is that "workflows targeting unsupported runners may remain queued or fail." The way to find out when your version expires is an endpoint GitHub added on 3 September. I queried it today: gh api repos/OWNER/REPO/actions/runners/deprecations/2.335.1 # {"runner_version":"2.335.1","runtime_deprecates_at":"2026-09-24T15:30:55Z"} gh api repos/OWNER/REPO/actions/runners/deprecations/2.337.0 # {"runner_version":"2.337.0","runtime_deprecates_at":null} A runner on 2.335.1 falls out of support on 24 September, so by the time full enforcement starts on the 25th it no longer gets jobs. You get the installed version on the machine itself with ./config.sh --version. The self-hosted checklist, in order: 1. Update the runner to the current release and leave auto-update on. If it's off, you need a manual cadence shorter than 30 days. 2. Rebuild the images, VM templates and containers you create runners from. The changelog asks you to recreate runners built from older cached images or templates: if the image ships a runner older than 2.329.0, it won't register. 3. Linux ARM32: move to arm64. Node 24 has no official support there, the runner's linux-arm package only bundles the Node 20 binary (externals.sh), and the code that fails the step is already in the runner. 4. macOS 13.4 or earlier: upgrade the OS. GitHub lists Node 24 as incompatible with those versions. 5. Actions that already declare node24 (such as actions/checkout@v5) require runner v2.327.1 at minimum. If you've done step 1, you already have it. 4. Retention, 1 October: history gets deleted This change breaks no build, but it deletes data. According to the 27 August changelog post, starting 1 October 2026 checks, workflow runs and statuses follow the same retention setting that already governed artifacts and logs, 90 days by default. Until now they were kept for "400+ days" regardless of your configuration. - For public repositories the maximum is 90 days. - The change is not retroactive: raising retention later won't restore anything. - Check and workflow run metadata isn't billed as storage; the associated artifacts and logs are. What breaks is anything that reads that history beyond your period: deployment metrics computed from gh run list, audits that link to a specific run, quarterly reports. Export what you need before 1 October. What the changelog does not say: what happens to a pull request open for more than 90 days whose required check expires. I couldn't find it documented. If you have long-lived PRs under branch protection, look at them after 1 October before assuming anything. 5. Images: macos-14 has a date, ubuntu-22.04 doesn't yet macos-14 (actions/runner-images#13518): deprecation started on 6 July and support ends on 2 November 2026. Before that there are eight brownouts during which jobs fail: 5, 12, 16, 19, 23, 26, 29 and 30 October, each from 14:00 UTC to 00:00 UTC the next day. It covers macos-14, macos-14-large and macos-14-xlarge. The replacement is macos-15, macos-26 or macos-latest, which started pointing to macOS 26 with the migration that began on 15 June. ubuntu-22.04 (actions/runner-images#14254): on 17 September deprecation only begins. The issue warns about longer queue times at peak hours, not failures. Retirement is on 17 April 2027, with brownouts that the issue schedules for March and April (no year given, but they fall before the retirement). It also covers ubuntu-22.04-arm. The replacement is ubuntu-24.04, ubuntu-26.04 or ubuntu-latest. If someone tells you ubuntu-22.04 breaks this week, it doesn't. Migrate calmly, but don't leave it for March. What's in the changelog and breaks nothing - cache-mode (10 September): a new key to limit cache access per workflow or job. It's opt-in: workflows that don't set it "continue to use the existing secure defaults." - The separate Code Quality path (20 August): only affects you if you have reports filtering on dynamic/github-code-scanning/codeql or on the github-advanced-security actor. - actions/checkout and pull_request_target (18 June): this one does break, but it already happened. Since 20 July the floating tags, @v4 included, refuse to check out fork code in pull_request_target. If your workflow depended on that, it's already red. It's the same family of risk I covered in the worm that got in through npm trusted publishing. What I'm doing with my CI Nothing urgent. actions/checkout@v4 and actions/setup-node@v4 have been running on Node 24 since June, the workflow doesn't use the opt-out variable, it runs on ubuntu-latest (currently ubuntu-24.04) and on GitHub-hosted runners. 23 September changes nothing for it. I am going to bump both actions to their current major, for two reasons unrelated to the date: a permanent yellow annotation trains you to ignore annotations, and an action its authors no longer test on the runtime it actually runs on is debt. If you're also thinking about your own project's Node rather than the actions', I covered that in what breaks when upgrading to Node 26. Frequently asked questions What happens to GitHub Actions on 23 September 2026? GitHub removes Node 20 from the runners. Since 16 June 2026 runners had already been running actions on Node 24 by default, and ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true let you go back to Node 20. From 23 September that variable stops working and every JavaScript action runs on Node 24. Self-hosted runners on Linux ARM32 lose support, because Node 24 has no official support for that platform. Does an action that declares using: node20 fail after 23 September? Not for declaring it. The runner code (HandlerFactory.cs and NodeUtil.cs in actions/runner) rewrites node12 and node16 to node20, and node20 to node24; in the final phase it ignores the environment variables and always picks Node 24, without returning an error. The action fails if its code uses something Node 24 removed, such as tls.createSecurePair or fs.truncate with a file descriptor, or if it runs on a self-hosted Linux ARM32 runner or on macOS 13.4 or earlier. The logs show the annotation: The following actions target Node.js 20 but are being forced to run on Node.js 24. Do I have to update actions/checkout@v4 and actions/setup-node@v4? It's a good idea, but no date forces it. The v4 tags of both declare node20 and v5 onward declare node24. On GitHub-hosted runners, the v4 tags have already been running on Node 24 since June and keep working. When bumping setup-node, read the release notes: v5 turned on automatic caching when package.json has packageManager, and v6 limited it to npm. How do I find affected workflows across my organization? With GitHub code search: gh search code --owner YOUR_ORG with ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION, with ubuntu-22.04 path:.github/workflows, with macos-14 path:.github/workflows, and with node20 --filename action.yml for your own actions. The API only searches the default branch and allows 10 requests per minute. Labels that come from variables or expressions don't show up in any search: for those, check the Image: line of the Set up job step in the log. Does ubuntu-22.04 break on 17 September 2026? No. On 17 September deprecation only begins, and the notice talks about longer queue times at peak hours. Jobs start failing during the brownouts that issue 14254 in actions/runner-images schedules for March and April, before the image is retired on 17 April 2027. The one with a date this fall is macos-14: brownouts in October and retirement on 2 November 2026. What do self-hosted runners need to do before 25 September? Update the runner. From 25 September 2026 GitHub requires version 2.329.0 to register a runner, and each new release must be installed within 30 days to keep receiving jobs. There are brownouts first on 14, 16 and 18 September, from 11:00 AM to 3:00 PM Eastern. The endpoint GET /repos/OWNER/REPO/actions/runners/deprecations/VERSION returns when your version stops running jobs. Also rebuild the images you create runners from. What changes with GitHub Actions retention on 1 October 2026? Checks, workflow runs and statuses start following the artifact and log retention setting, which defaults to 90 days; before, they were kept for more than 400 days. For public repositories the maximum is 90 days, and the change is not retroactive. If you have metrics or audits that read old runs, export the data before that date. --- Originally published on ortamarco.me.

  3. Zenn · · @nekoai_lab

    【要対応】Node.jsの脆弱性10件が修正。Claude Code・MCP利用者が今すぐ確認すべきこと TL;DR 2026年10月21日、Node.jsの深刻な脆弱性を含む10件が修正された。 修正版は v22.23.4 / v24.22.1 / v26.12.1。該当する古いNodeを使っているなら更新が必要 Nodeを更新しただけでは終わらない。 自分のMacでは、修正前のNodeで動くプロセスが19個残っていた Claude Code本体、MCPサーバー、Cursor CLI、公開アプリでは確認・更新する場所が異なる 本番のDockerイメージも、再ビルド・再デプロイしない限り古いまま残る この記事では、今すぐ確認する手順、実際に対応した結果、同じことを繰り返さないための仕組みをまとめた Node を上げた。19個、直っていなかった 10月23日、仕事のチャットに「Node.js が危ないのでアップデート必須。Claude Code を使っている人はガッツリ関係する。会社単位じゃなく個人単位で」と連絡が回ってきた。 自分も最初はこう思った。 「Claude Code って Node で動いてるんだっけ?」 「本体は勝手に更新されるんでしょ?」 「GitHub も見るの? GCP は? Vercel は?」 調べてみたら、答えはどれも「半分合ってる」だった。 自分の Mac を見たら、Node は修正前の 26.10.0 のまま。brew upgrade node で 26.12.1 に上げた。これで終わり、と思った。 念のためプロセスを数えた。 修正前の Node で動き続けているものが、19個あった。 Astro の dev サーバーが10個。Next.js が4個。Claude Code が裏で起動した MCP サーバーが5個。いちばん古いものは1週間前から動いていた。しかも最初に打ったコマンドでは、そのうち3個が見えていなかった。 極めつけは、この調べものに使っていた Claude Code 自身だ。スマホから使うために Mac mini で常駐させていた Remote Control の親プロセスが、1週間前の 2.1.292 のまま動いていた。claude update で新しい版を入れても、そこから起動するセッションは古いままだった(後述)。 アップデートは、インストールした時点では終わらない。この記事は、その話だ。 自分はどれ? 3つの質問で判定する 質問 はい → いいえ → ① Claude Code・Cursor・Codex などの AI ツールを使っている? A に該当。次の質問へ 次の質問へ ② ターミナルで node -v を打つと、バージョンが出る? B に該当。次の質問へ 次の質問へ(ただしアプリ同梱の Node は残る。後述) ③ Node で作ったアプリやサーバーを、ネットに公開している? C に該当 — そのうえで、node -v や本番の Node が v18・v20・v23・v25 なら D も当てはまる。修正版が出ない。 区分 誰 やること 所要 A AI ツールを使っている人 Claude Code・Cursor CLI・デスクトップアプリを最新に 5分 B PC に Node が入っている人(MCP サーバー、npx、npm で入れた CLI、dev サーバー) Node を更新して、動いているものを再起動 10〜20分 C Node のアプリを公開している人 本番の実行環境を更新して再デプロイ 環境しだい D Node 20 以前などを使っている人 22 / 24 系に乗り換え 半日〜 Claude Code 本体だけ使っている人は、まず A。たぶんこの層がいちばん多い。 ただ、Claude Code を入れるときに「Node も入れてね」と言われて Homebrew で入れた人は多いはず。その場合は B も当てはまる。node -v を一回打ってみてほしい。 結局、どこを見ればいいのか 見る場所は3層ある。 層 何を見る 見落としやすいもの ① PC(持っている台数ぶん) Claude Code の版、node -v、動いている Node、アプリ同梱の Node 普段使わないPC、起動しっぱなしの dev サーバー ② リポジトリ Dockerfile の FROM node:..、.nvmrc / .node-version、package.json の engines、GitHub Actions の node-version FROM node:20 のまま、node-version: 24.14.0 みたいな細かい固定 ③ 本番 実際に動いているランタイムの版 「Dockerfile は直したけど、まだデプロイしてない」 ②はリポジトリの置き場所でこう打てば一覧が出る。 grep -rnE "FROM node|node-version|\"node\":" \ --include=Dockerfile* --include=.yml --include=.yaml \ --include=package.json --include=.nvmrc --include=.node-version \ --exclude-dir=node_modules . 自分のリポジトリ(10本ちょっと)でやったら、こうなった。 本番が Cloud Run(自前の Dockerfile)で FROM node:20-alpine のもの:1本。修正が出ない系列。最優先 本番が Cloud Run で FROM node:22-alpine のもの:3本。イメージを作り直して再デプロイ CI で node-version: 24.14.0 に固定しているもの:1本。本番は Cloudflare Workers なので Node では動いていない。CI だけ上げればいい Python のサービス、静的サイト(Firebase Hosting):本番は関係なし CI(GitHub Actions)は、外から来たデータを展開する場面が少ないので優先度は低い。でも node-version の固定は、ついでに上げておく。 そもそも何が直ったのか Node.js の公式発表では、10件のうち4件が深刻度「高」だった。 CVE ざっくり言うと 危ない場面 CVE-2026-111497 Brotli(圧縮形式の一つ)を展開するとき、メモリの範囲外に書き込める 外から来た Brotli データを Node が展開するとき CVE-2026-106069 0127.0.0.1 みたいな変な書き方の IP アドレスで、TLS 証明書のチェックがずれる 接続先のホスト名を外部入力で決めていて、かつ通信経路を握られているとき CVE-2026-105483 全角数字の IP アドレスで、TLS 証明書のチェックがずれる(24 / 26 系だけ) 同上 CVE-2026-105482 dns.resolve が名前の途中の NUL 文字(\0)で切れる 「許可リストで名前をチェックしてから名前解決する」作りのとき 残り6件は中・低。HTTP の keep-alive、crypto.randomUUID()、権限モデルまわりなど。 修正版は v22.23.4 / v24.22.1 / v26.12.1。回避策は出ていない。上げるしかない。 Brotli のバグは、理屈の上では任意のコード実行までつながる種類のものだ。Node の発表でも「リモートからのコード実行を許す」と書かれている。ただ、10月23日時点で GitHub に公開されている PoC と解析を見ると、5本のうち4本は「リモートからのコード実行は成立していない。実際にはプロセスを落とせる(DoS)」という結論だった(条件を固めたローカル環境でなら実行できた、という報告はある)。落とせるだけでも、サーバーなら十分困る。 どんな場合に危ないのか ここがいちばん混乱しているところだと思う。 TLS と DNS の3件は、条件がかなり狭い。「接続先を外部入力で決めるサーバーを作っている」「経路上に攻撃者がいる」くらいの前提が要る。普通に使っているだけなら、ほぼ当たらない。 問題は Brotli の1件だ。こっちは入口が広い。 当たる:Node が外部のサイトやAPIを fetch() する Node の fetch() は、HTTPS のとき「Brotli で圧縮して送ってくれていいよ」とサーバーに伝える。サーバーが Brotli で返してきたら、中身を自動で展開する。 つまり、相手のサーバーが悪意を持っていたり、乗っ取られていたりしたら、fetch() しただけで細工したデータが届く。 当てはまるもの: URL を取りに行く MCP サーバー(Web取得、ブラウザ操作、スクレイピング系) 外部 API を叩くスクリプトやバッチ Claude Code に作らせた「URLを入れると要約するツール」みたいなもの 当たる:外からのリクエスト本文を展開するサーバー Express 5(body-parser 2.x)は、express.json() などのパーサーを使っていると、Content-Encoding: br のリクエスト本文を既定で展開する。 Express で API を公開しているなら、C に該当する。 ちなみに公開されている PoC(攻撃の実証コード)の一部も Express を使っている。ただし受け取った本文を自前の処理で Brotli 展開する作りで、body-parser の既定の動作を狙ったものではない。 当たる:受け取ったファイルを zlib で展開する アップロードされた .br ファイルを展開する、みたいな処理。これも外から来たデータの展開だ。 ほぼ当たらない:自分のPCの中だけで完結するもの localhost でしか見ていない dev サーバーが、外部と通信していないなら、細工したデータが届く経路がない。 ただし「dev サーバーの中で外部 API を fetch() している」なら当たる。Next.js のサーバー側で外部APIを呼んでいる、とか。けっこうある。 当たらない:Node を入れているだけ インストールされているだけで、何も動いていなければ攻撃は届かない。ただ、MCP サーバーは Claude Code を開くたびに裏で起動する。「動かしていないつもり」が一番あぶない。 Claude Code 本体の話:Node じゃなくて Bun 本体は Node を使っていない Claude Code の公式ドキュメントには、「インストールされた claude は Node を呼び出さない」とある。npm で入れた場合も、中身は同じネイティブバイナリだ。 だから「Node を更新すれば Claude Code 本体が直る」は、正しくない。 中の Bun にも、同じ Brotli のコードが入っていた Claude Code のネイティブ版は Bun で作られている。 その Bun にも、同じ Brotli ライブラリが入っていた。Bun の issue #46352 では「全リリースが影響を受ける」とされ、10月22日に修正がマージされた(日本時間11時ごろ)。修正を含む Bun v1.4.4 のリリースは同日19時ごろだ。 じゃあ Claude Code 本体で攻撃が届くのか。ここは分からない。Claude Code の中で、外から来た Brotli データを Bun が展開する場面があるのか。Anthropic は何も公表していない。 2.1.308 に上げた。直ったかは分からない 自分の Mac に入っていたのは 2.1.306。公開は10月21日(日本時間12時ごろ)で、Bun の修正より前だ。 claude update を打ったら 2.1.308 になった。 $ claude update Current version: 2.1.306 Updating to 2.1.308... Successfully updated from 2.1.306 to version 2.1.308 2.1.308 の公開は10月22日の13時半ごろ。Bun の修正マージよりは後、Bun v1.4.4 のリリースよりは前。リリースノートは「Bug fixes and reliability improvements」の1行だけだった。 なので言えるのは「最新にしておくのが、いまできる最善」まで。直ったとは言えない。 「本体は勝手に更新される」は本当か 入れ方による。公式ドキュメントを読むと、こうなっている。 入れ方 自動で更新される? 手で上げるコマンド 公式インストーラー(curl ... install.sh) される claude update npm される(書き込み権限があれば) npm install -g @anthropic-ai/claude-code@latest Homebrew(brew install --cask claude-code) 既定ではされない brew upgrade claude-code WinGet 既定ではされない winget upgrade Anthropic.ClaudeCode apt / dnf / apk されない OS のアップデートで Homebrew と WinGet は、CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1 を設定すると Claude Code が代わりに更新してくれる。 自分の Mac は公式インストーラー版で、9/26・10/7・10/16・10/22 と更新されていた。 それでも 2.1.306 で止まっていた。自動更新は「起動したときと、動いている間にときどき」見に行って、次に起動したときに切り替わる仕組みだからだ。急ぐなら claude update を手で打つ。 ちなみに配布には latest と stable(ふだんは約1週間遅れ)の2つのチャンネルがある。今回は10月22日の時点で、stable も 2.1.308 になっていた。 逆に Node は、誰も勝手に更新してくれない。Homebrew で入れたなら、brew upgrade を打つまで永遠に古いままだ。 手順 まず一発で確認する PC ごとに、これをターミナルに貼る。普段使わない PC も忘れずに。 Mac / Linux: echo "== Claude Code"; claude --version 2>/dev/null || echo "入っていない" brew list --cask 2>/dev/null | grep '^claude-code' && echo "↑ Homebrew 版。既定では自動更新されない" echo "== Node"; which -a node 2>/dev/null; node -v 2>/dev/null || echo "入っていない" echo "== Cursor CLI"; cursor-agent --version 2>/dev/null || echo "入っていない" echo "== 動いている Node"; ps -axo pid,lstart,command | grep -E '[n]ode |[n]ext-server' | cut -c1-150 Windows(PowerShell): "== Claude Code"; claude --version "== Node"; where.exe node; node -v "== 動いている Node"; Get-Process node -ErrorAction SilentlyContinue | Select-Object Id, StartTime, Path 最後の「動いている Node」の読み方: 起動時刻が Node をアップデートする前 → 古い Node で動いている。再起動する パスが /Applications/〜.app/ の中 → アプリが同梱している Node。アプリの更新を待つ パスが Homebrew の Caskroom の中 → Homebrew で入れたツール(Cursor CLI など)が同梱している Node。brew upgrade --cask <名前> で上がることがある コマンドの引数に node という文字を含むだけの別のプロセスも出てくる。パスを見て判断する next-server → Next.js の dev サーバー。名前に node が付かないので、grep node だけだと見逃す。自分はこれで3個見逃した 「入っていない」と出ても、それは PATH の上に無いというだけだ。アプリ同梱の Node、Docker、本番は別に見る(下の「Node を同梱しているアプリ」と C)。 A:AI ツール本体 claude update claude --version # 2.1.308 以降を推奨(修正が入ったかは未公表) Homebrew で入れた人は brew upgrade claude-code(latest 版なら claude-code@latest)、WinGet なら winget upgrade Anthropic.ClaudeCode。 開いている Claude Code は、一度閉じて開き直す。動いているプロセスは古い版のままだからだ。 Remote Control(claude remote-control)を常駐させている人は要注意だ。自分の Mac mini では、スマホから使うために claude remote-control を tmux の中で常駐させていた。これが10月16日に起動したまま、当時の 2.1.292 で動き続けていた。スマホから新しく作ったセッションも、全部この 2.1.292 で起動していた。claude update で 2.1.308 を入れても、常駐している親が古いままなら子も古い。 常駐している親ごと止めて、起動し直す。自分の環境(tmux セッション名 claude、LaunchAgent で起動)ならこうだ。動いているリモートのセッションは全部切れるので、作業が終わってから。 tmux kill-session -t '=claude' launchctl kickstart gui/$(id -u)/com.nekoai.remote-control # 自分で作った LaunchAgent の名前 ps -axo lstart,command | grep '[c]laude/versions/' # 新しい版で動いているか確認 Cursor を使っているなら CLI も。中に自前の Node を抱えている。 cursor-agent update # 公式インストーラーで入れた場合 brew upgrade --cask cursor-cli # Homebrew で入れた場合 自分の Mac では、cursor-agent update で 2026.10.22 版になって、中の Node が v26.8.1 から v26.12.1 に上がった。 デスクトップアプリも見ておく。10月23日、もう1台の Mac で Claude のデスクトップアプリを開いたら「Claude has an important security update」と出た。Mac mini のほうは、気づいたときには自動で入っていた。ChatGPT のデスクトップアプリにもアップデートが来ていた。中身が今回の件かは、どちらも公表されていない。Claude アプリの更新版(3.6556.1)の中の Node は v24.22.0 で、Node の修正版ではなかった。アプリのログを見ると、アプリに内蔵されている Claude Code が 2.1.304 から 2.1.308 に上がっていた。アプリの「新着情報」(10月22日付)には Node や Brotli の記載はなく、作業フォルダの権限や SSH まわりの変更・修正が並んでいた。「important」はそっちの話なのだと思う。それでも「important」と出ているなら入れる。 Mac の Claude アプリなら、画面上のメニューバーの「Claude」→「アップデートを確認…」→「再起動してアップデート」。アプリの中のアカウントメニューには無いので注意。 B:PC の Node まず、どこから入れた Node かを確認する。 which -a node ls -l "$(which node)" node -v ls -l の結果 入れ方 更新コマンド .../Cellar/node/... を指している Homebrew brew update && brew upgrade node ~/.nvm/... nvm 下を参照 fnm を含むパス fnm fnm install 24 && fnm default 24 ~/.volta/... Volta volta install node@24 mise を含むパス mise mise use -g node@24 /usr/local/bin/node で Cellar を指していない 公式インストーラー nodejs.org から最新版を入れ直す /usr/local/bin/node は、Intel Mac の Homebrew でもここに入る。ls -l で Cellar を指していたら Homebrew だ。Homebrew で node@22 や node@24 を入れているなら、brew upgrade node@24 のように指定する。 nvm は、入れるだけだと足りない。 nvm install 24 --reinstall-packages-from=current # npm -g で入れたものも引き継ぐ nvm alias default 24 nvm use 24 # 今のターミナルも切り替える プロジェクトの .nvmrc や、MCP の設定ファイルに ~/.nvm/versions/node/v22.x/... のような絶対パスを書いていたら、そっちも直す。古い版は nvm uninstall <古い版> で消せる。fnm・Volta・mise も、固定や絶対パスの事情は同じだ。 Windows はこう。 where.exe node の結果 入れ方 更新 C:\Program Files\nodejs\node.exe 公式インストーラー or winget winget list OpenJS で ID を確認して winget upgrade <ID>。無ければ nodejs.org から入れ直す ...\nvm... や nodejs へのリンク nvm-windows nvm install 24.22.1 → nvm use 24.22.1 .volta / fnm を含むパス Volta / fnm Mac と同じ winget の ID は、LTS 版なら OpenJS.NodeJS.LTS、最新版なら OpenJS.NodeJS。入れている方を上げる。 上げたら確認。 node -v v26.12.1 / v24.22.1 / v22.23.4 以降ならOK(v23・v25 は修正が出ないので D へ) ここからが本番。「まず一発で確認する」の最後の行をもう一度打つ。アップデートより前に起動したものは、全部古い Node で動いている。 Claude Code(ターミナル)→ /exit して起動し直す。裏の MCP サーバーも新しい Node で起動し直す Claude デスクトップアプリ・VS Code・Cursor → これらも MCP サーバーを起動している。ウィンドウを閉じるだけでなく、⌘Q(Windows はタスクトレイから終了)で完全に終了してから開き直す dev サーバー → Ctrl+C で止めて、もう一度起動 最後に、もう一度プロセスを確認する。自分で入れた Node のプロセスに、アップデートより前の起動時刻が残っていなければ、B は完了だ(アプリ同梱のものは残っていい)。 Node を同梱しているアプリ 自分で入れた Node とは別に、アプリが自前の Node を抱えていることがある。自分の Mac で、10月23日に見た版はこうだった。 アプリ 中の Node の版 Cursor の CLI(cursor-agent) v26.8.1 → 更新後 v26.12.1 Antigravity(Electron) v24.20.0 Grok Bot(SpaceXAI と Cursor が出しているアプリ。署名は Cursor の会社の Anysphere。Electron) v24.19.0 Claude デスクトップアプリ(Electron、更新版 3.6556.1) v24.22.0 ChatGPT デスクトップアプリ(中の補助ツール) v24.21.0(アプリ更新後も同じ) 版が修正前でも、そのアプリで攻撃が届くとは限らない。外から来た Brotli データをその Node で展開する場面があるかは、確認できていない。 Electron(VS Code、Cursor、Antigravity、Slack などの土台)は、10月23日時点の最新リリース(v45.0.0、10月21日)でも中の Node は 24.22.0 で、修正版ではなかった。 Cursor CLI 以外の同梱 Node は、こっちでは直せない。アップデートが来たら、すぐ入れる。それだけ。 Codex CLI は本体が Rust 製で、今回は関係ない。npm で入れた場合も、入口の小さなスクリプトが Node で動くだけだ。 C:公開しているアプリ 本番がどこで動いているかで変わる。 実行環境 自動で直る? やること Vercel 管理ランタイムに順次展開中。WAF ルールも入った Node 20 を使っているなら手動で移行。WAF は外向きの fetch() のレスポンスまでは守れない AWS Lambda 既定(Auto モード)なら自動で段階適用 Manual / Function update モードやコンテナイメージなら、再デプロイ・再ビルド Cloud Run(関数として、または --base-image 指定でソースからデプロイ) 自動ベースイメージ更新が有効なら反映される 設定を確認。今回の版の反映告知はまだ見つからなかった Cloud Run(自分の Dockerfile) 直らない 下の Docker と同じ。作り直して再デプロイ Firebase Functions 第2世代は Cloud Run の関数と同じ仕組みで、自動更新が既定で有効のはず コンソールでランタイムを確認し、心配なら再デプロイ Cloudflare Workers / Firebase Hosting(静的) 本番は Node で動いていない ビルドに使う Node だけ上げる Docker 公式イメージ node:22 / 24 / 26 は更新済み イメージを作り直して再デプロイ。node:20 は更新されない GitHub Actions setup-node の配布元には登録済み check-latest: true にするか、バージョンを固定で書く。ランナーのキャッシュに古い版が残っていることがある 自前サーバー 直らない B と同じ手順で更新して、プロセスを再起動 Docker は見落としやすい。FROM node:24 と書いてあっても、ビルドし直さないと古いイメージのままだ。 D:Node 20 以前を使っている Node 20 は2026年4月30日にサポートが終わっている。18・23・25 も終わっている。今回の修正は出ないし、今後も出ない。 Homebrew の node@20 は、10月28日に使えなくなる予定だ。 22 系か 24 系(どちらも長期サポート。26 系も10月28日に長期サポートに入る予定)に移す。破壊的変更があるので、テストを回しながら進める。今日すぐ終わる作業ではないので、それまでは「外部から来たデータを展開する処理」を止められないか考えておく。 確認コマンドと完了の判断は、教材にもまとめた。 nekoai-lab.github.io 時系列で見る 日時(日本時間) 出来事 2026年2月27日 Google の brotli リポジトリに、今回の修正のもとになるコミットが入る 10月14日 Node.js が「21日以降にセキュリティリリースを出す」と予告 10月21日 12時ごろ Claude Code 2.1.306 公開 10月21日 19時38分 Node.js v22.23.4 / v24.22.1 / v26.12.1 公開 10月22日 0時47分 最初の PoC リポジトリが GitHub に作られる 10月22日 4時27分 Google brotli が v1.2.1 / v1.1.1 / v1.0.10 を公開 10月22日 11時ごろ Bun が Brotli の修正をマージ 10月22日 13時半ごろ Claude Code 2.1.308 公開 10月22日 午後 Homebrew の node / node@24 / node@22 の修正版が配布される 10月22日 19時ごろ Bun v1.4.4 リリース 10月22日 23時ごろ CISA の評価は「悪用は確認されていない」 いくつか補足。 Brotli のバグは Node 固有じゃない。Google の brotli ライブラリ(1.0.3〜1.2.0)のバグで、CVE の採番も Google だ。CVSS 4.0 では 9.2(Critical)と付いていて、Node 側の評価は High。Brotli を使っている他のソフトも、同梱しているバージョンしだいで影響を受ける。 Node の発表では、Node への報告者として OpenAI の Codex Security チームの名前が載っている。上流の修正は2月に Google の開発者が入れていた。誰が最初に見つけたのかは、どの一次ソースにも書かれていない。 修正のもとになるコードは2月に brotli のリポジトリに入っていた。ただ、そのときはセキュリティ修正として出ていない。セキュリティ修正として世に出たのは10月21日の Node が最初で、brotli 自体のリリースはその翌日だった。 やった結果(実録) 対応は10月23日の朝の数時間。Mac 2台と本番4本。 対象 前 後 Mac mini の Node(Homebrew) v26.10.0 v26.12.1 Mac mini の Claude Code 2.1.306 2.1.308 Mac mini の Cursor CLI 同梱の Node v26.8.1 v26.12.1 Mac mini の Claude デスクトップアプリ 3.1623.4 3.6556.1(自動で更新。内蔵の Claude Code は 2.1.308) Mac mini の起動しっぱなしの Node 19個(最初のコマンドでは16個しか見えなかった) dev サーバー14個を止めた(親のプロセスも含めて15個を kill)。Claude アプリの MCP 1個はアプリの再起動で消えた。残りの MCP 4個は Remote Control を起動し直して消す MacBook Pro の Claude Code 2.1.283(9月26日の版) 2.1.308 MacBook Pro の Node v24.14.0(公式 pkg) Homebrew の v26.12.1 に切り替え MacBook Pro の起動しっぱなしの Node(全部 MCP サーバー) 11個(うち5個は9月19日から) 0個 本番(Cloud Run 4本) node:20 1本、node:22 3本 全部 22.23.4 で作り直してデプロイ。版チェック入り ChatGPT・Antigravity・Grok Bot の同梱 Node 修正前 アプリの更新待ち MacBook Pro はひどかった。 Claude Code は1か月前の版。Node で動いていた MCP サーバー11個のうち5個は、1か月以上前の9月19日から動き続けていた。その中には、ブラウザでページを取りに行く playwright-mcp もいた。今回のバグにいちばん当たりやすい種類だ。 Mac を閉じずにスリープで使い続けていると、こうなる。自動更新は「次に起動したとき」にしか効かない。 再発防止:「入れた」と「動いている」の差を仕組みで埋める 今回の穴は、どれも同じ形をしていた。 Node は上げた。でも動いているプロセスは古いまま Claude Code は上げた。でも常駐している親は古いまま Dockerfile は合っている。でも本番のイメージは作り直していない 自動更新はある。でも閉じないから効かない 人が気をつけて防ぐのは無理だと思った。なので仕組みにした。 1. Node の入れ方を全部の Mac で揃えた Mac mini は Homebrew、MacBook Pro は nodejs.org の公式インストーラーだった。入れ方が違うと、更新の手順も違う。だから後回しになる。 MacBook Pro を Homebrew に切り替えた。npm ls -g --depth=0 で、公式インストーラー側に自分で入れたツールが無いことを確かめてから。 以下は Apple Silicon の Mac(Homebrew が /opt/homebrew)で、公式インストーラーから乗り換える場合だ。Intel Mac は Homebrew 自体が /usr/local に入るので、この rm を打つと Homebrew の Node まで消える。先に brew --prefix が /opt/homebrew と出ること、ls -l /usr/local/bin/node が Cellar を指していないことを確かめる。 brew install node 公式インストーラーの Node を消す(npm -g で入れたものがあれば、先に控えておく) sudo rm -rf /usr/local/bin/node /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack \ /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack \ /usr/local/include/node /usr/local/share/doc/node /usr/local/share/man/man1/node.1 ターミナルを開き直して which -a node # /opt/homebrew/bin/node だけになれば OK これで、どの Mac も brew upgrade node の1行で済む。 2. 本番の Dockerfile に「古い Node なら作らない」を入れた FROM node:22-alpine と書いていても、作り直さなければ古いまま。逆に、キャッシュや取り違えで古いイメージが混ざることもある。 なので、本番のイメージを作るときに Node の版を確かめて、修正前なら止めるようにした。実行ステージの FROM の直後に1行足すだけだ。 FROM node:22-alpine AS runner Node.js 2026-10-21 のセキュリティ修正より前の版では作らない RUN node -e "const m={22:[23,4],24:[22,1],26:[12,1]};const [a,b,c]=process.versions.node.split('.').map(Number);const min=m[a];const ok=a>26||(min&&(b>min[0]||(b===min[0]&&c>=min[1])));if(!ok){console.error('Node '+process.version+' は修正より前か、サポート外');process.exit(1)}" 22.23.4・24.22.1・26.12.1 より前と、20 系などの修正が出ない系列は、ここでビルドが止まる。4本とも入れて、作り直し、デプロイまで通した。 このチェックが効くのは、作り直すときだけだ。作り直していない本番が古いままなのは、これでは分からない。 次は Dependabot に FROM node:.. を見張らせて、新しいイメージが出たら PR が自動で立つようにする(これから)。 3. 週1回、まとめて上げて「古いまま動いているもの」を探す 2つのスクリプトを作った。 weekly-update:Homebrew で入れたもの全部(Node 以外も)、Claude Code、Cursor CLI を上げる。macOS のアップデートが来ていれば知らせる(入れはしない。再起動が要るから) stale-procs:更新したのに、古い版のまま動いているプロセスを探す stale-procs の考え方は単純だ。プロセスの実行ファイルを lsof で調べて、いまの node の実体と違えば古い。Homebrew のものなら、Cellar/<名前>/<版> が opt/<名前> の指している版と違えば古い。Node や Python に限らず、Homebrew で入れたもの全部に使える。 Node だけなら、これで同じことが分かる。 cur=$(realpath "$(command -v node)") for pid in $(pgrep -f 'node|next-server'); do exe=$(lsof -a -p "$pid" -d txt -Fn 2>/dev/null | sed -n 's/^n//p' | head -1) case "$exe" in /Cellar/node/bin/node|/usr/local/bin/node|/.nvm//bin/node) [ "$(realpath "$exe" 2>/dev/null || echo "$exe")" != "$cur" ] && echo "$pid 古い Node: $exe" ;; esac done next-server のように名前を変えているプロセスも、実行ファイルで見るので拾える。アプリ同梱の Node は、自分では直せないので数えない。 ただし、これで分かるのは Homebrew と nvm で入れた Node だけだ。公式インストーラーの Node は同じ場所に上書きされるので、この比べ方では新旧を見分けられない。fnm・Volta・mise も、この短い版では対象にしていない。その場合は、プロセスの起動時刻が Node を上げた時刻より前かどうかで判断する。 結果はスマホに通知する。ただ、普段から通知は短すぎて読めていなかった。「更新されたな」と分かるだけで、何をすればいいかは分からない。なので、通知だけで次の行動が決まる形にした。 古い版のまま動いているもの 9 件 ・Remote Control・アプリから起動したセッション 4件(claude 2.1.292)  → 親(Remote Control)を作り直すか、そのセッションを閉じる ・MCP サーバー 4件(mcp-pdf-server/node 26.10.0_1)  → 起動したアプリやセッションを開き直す ・Remote Control の親 1件(claude 2.1.292)  → tmux のセッションを作り直す 4. 通知に「プロセスの引数」を出してはいけなかった このスクリプトは Claude Code で書いて、レビューは別の AI(Codex)にさせている。そのレビューで2回止められた。 1回目は、通知にプロセスのコマンドをそのまま載せていた。node app.js --password xxx のような起動のしかたをしていたら、パスワードがログとスマホの通知に流れる。 2回目は、それを直したつもりで「引数のうち、ハイフンで始まらない最初のもの」をスクリプト名として出していた。node -e 'key="..."' のように中身を直接渡すと、そのコードが名前として出る。 最後は「実在するファイルのときだけファイル名を出す。それ以外は node」にして通った。 プロセスの一覧を通知やログに出すスクリプトは、引数を出した時点で秘密が漏れる道になる。自分のレビューだけだったら、2回とも素通りしていたと思う。 5. 片付けをどこまで自動にするか(これから) 通知が来ても、止めるのが人の手作業なら、結局また溜まる。かといって、全部を自動で止めると作業中の dev サーバーやセッションが切れる。なので3段階に分けた。 段階 対象 誰が 全自動 起動した元がもういなくて取り残された古いプロセス。使われていないときの Remote Control の親 週1回の自動実行。止めたものは通知で報告 ひと言で 開いているセッションの MCP、dev サーバー 通知を見て、AI に「古いの片付けて」と頼む。一覧を見せて確認してから止める 人が決める デスクトップアプリ、macOS のアップデート 自分。通知に「何をすればいいか」が書いてある MacBook Pro の11個(うち5個は9月19日から)は、たぶん「全自動」で消える種類だった。これを作れば、今回の後片付けの大半は、寝ている間に終わる。 この部分は、設計まで書いて Codex に引き継いだ。 32リポジトリぶんの依存を、そのあとどう見回るようにしたかは、続きに書いた。 zenn.dev まとめ AI ツールを使っている人:claude update、Cursor CLI、デスクトップアプリを最新に。開き直す Node が入っている人:Node を上げて、動いているものを全部再起動。アプリ同梱の Node はアプリの更新で 公開している人:本番の実行環境を確認して、再デプロイ。Docker は作り直す Node 20 以前の人:今回は直らない。乗り換える 全員:「入れた」と「動いている」の差は、気合いでは埋まらない。週1回の更新と、古いまま動いているものの検出を仕組みにしておく アップデートは、インストールした時点では終わらない。 古いプロセスを止めた時点で、やっと終わる。 参考 Node.js — October 2026 Security Releases google/brotli v1.2.1 CVE-2026-111497(CVE.org API) oven-sh/bun #46352 — Brotli の脆弱性 Bun v1.4.4 ikow/brotli2crash — PoC と解析(実質 DoS とする例) hypnguyen1209/CVE-2026-111497 — Express を使った PoC Claude Code — セットアップ(ドキュメント) anthropics/claude-code — Releases expressjs/body-parser — read.js Electron v45.0.0 SpaceXAI and Cursor Launch Grok Bot Agent App in Beta(AI Weekly) Vercel — Node.js security advisory AWS Lambda — ランタイムの更新 Cloud Run — 自動ベースイメージ更新 actions/setup-node — check-latest Node.js リリーススケジュール

    4 likes
  4. Automatiser ses déploiements : le guide CI/CD efficace pour 2026 L'essentiel en une minute Automatiser ses déploiements, c'est confier à une machine la suite d'étapes qui sépare un git push de la mise en production : tests, build, livraison, et retour arrière si quelque chose casse. Pour un développeur seul ou une petite équipe, un fichier YAML d'une soixantaine de lignes suffit, et les offres gratuites de GitHub et de GitLab couvrent un projet de taille modeste. Ce guide donne un pipeline GitHub Actions complet, son équivalent GitLab CI, les quotas et les prix lus le 12 octobre 2026 sur les pages officielles, puis les stratégies de déploiement, les secrets et le rollback. Les fichiers ont été relus contre la documentation, pas exécutés. > Points clés > > - Pour les dépôts privés d'un même compte, GitHub Free inclut 2 000 minutes par mois. GitLab Free en inclut 400 par espace de noms. > - Au-delà du quota, GitHub facture 0,006 $ la minute sur son runner Linux 2 cœurs, GitLab vend des lots de 1 000 minutes à 10 $. > - Les exemples avec actions/checkout@v4 ou le mot-clé only de GitLab sont datés : les actions officielles en sont aux versions 7 et 8, et GitLab déclare only déprécié. > - L'approbation par un relecteur avant production n'existe pas sur un dépôt privé GitHub en offre Free, Pro ou Team. L'équivalent gratuit est un déclenchement manuel du même fichier. > - Un retour arrière se prépare avant l'incident : chez GitHub, une ancienne exécution ne peut être relancée que pendant 30 jours. --- CI, livraison continue, déploiement continu : trois niveaux Derrière le sigle CI/CD se cachent trois niveaux d'automatisation, et le bon niveau dépend de la confiance que vous accordez à vos tests. | Niveau | Ce qui est automatisé | Ce qui reste manuel | |---|---|---| | Intégration continue (CI) | Installation, lint, tests et build à chaque push ou demande de fusion | La livraison | | Livraison continue | La CI, plus la préparation d'une version prête à partir | Le clic qui envoie en production | | Déploiement continu | Toute la chaîne, jusqu'à la production | Rien, sauf le retour arrière | Le programme de recherche DORA, piloté par Google Cloud, mesure le résultat avec cinq indicateurs : délai de mise en production, fréquence de déploiement, temps de rétablissement après un déploiement raté, taux d'échec des changements et taux de reprise des déploiements, ou rework rate (guide des métriques DORA, mis à jour le 5 janvier 2026). --- Automatiser ses déploiements avec GitHub Actions Un seul fichier suffit : placé dans .github/workflows/, il décrit trois jobs enchaînés, test, build et deploy. L'exemple vise une application Node.js, comme l'API de notre tutoriel pour créer une API REST avec Node.js et Express. name: CI/CD on: pull_request: branches: [main] push: branches: [main] permissions: contents: read jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: 24 cache: npm - run: npm ci - run: npm run lint - run: npm test build: needs: test if: github.event_name == 'push' runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: 24 cache: npm - run: npm ci - run: npm run build - uses: actions/upload-artifact@v7 with: name: app path: dist/ retention-days: 7 deploy: needs: build if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 10 environment: name: production url: https://example.com concurrency: production steps: - uses: actions/checkout@v7 - uses: actions/download-artifact@v8 with: name: app path: dist - name: Déployer env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: bash scripts/deploy.sh dist La dernière ligne est volontairement neutre : scripts/deploy.sh représente la commande de votre hébergeur, rsync, CLI de plateforme ou kubectl. Notre comparatif des offres d'hébergement cloud pour développeurs aide à choisir la cible. Commencez ce script par test -d dist || exit 1 : un artefact expiré ne doit pas aboutir à un déploiement vide. Tel quel, ce fichier fait du déploiement continu, le troisième niveau du tableau : chaque push sur main part en production. Pour en rester à la livraison continue sans offre payante, ajoutez workflow_dispatch: sous on et réservez deploy à cet événement : le bouton « Run workflow » rejoue alors tests, build et déploiement dans la même exécution. Dans cette variante, seules ces lignes changent : on: pull_request: branches: [main] push: branches: [main] workflow_dispatch: jobs: build: needs: test if: github.event_name != 'pull_request' # reste du job inchangé deploy: needs: build if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' # reste du job inchangé La page sur le lancement manuel précise que le fichier doit se trouver sur la branche par défaut pour que le bouton apparaisse. Ce que fait chaque bloc Chaque mot-clé ci-dessous est décrit dans la référence de syntaxe des workflows de GitHub, consultée le 12 octobre 2026. - on. Le workflow tourne sur chaque demande de fusion (pull request) vers main, où seul le job test s'exécute, et sur chaque push sur main. - permissions. Le jeton GITHUB_TOKEN est limité à la lecture du code, le minimum que GitHub recommande. - needs et if. Un job ne démarre que si celui dont il dépend a réussi : un test rouge bloque le déploiement. Le if de build le réserve aux push, et deploy est ignoré quand build l'est. - timeout-minutes. Sans cette ligne, un job bloqué peut tourner 360 minutes, la valeur par défaut, et vider votre quota. - environment. Le job est rattaché à l'environnement production. Sur un dépôt privé en offre Free, retirez ce bloc et créez DEPLOY_TOKEN comme secret de dépôt : d'après la page de gestion des environnements, les environnements et leurs secrets y demandent une offre Pro, Team ou Enterprise. - concurrency. Deux déploiements vers la production ne s'exécutent jamais en même temps, et un déploiement en attente est remplacé par le plus récent. Les versions datent vite un tutoriel. Au 12 octobre 2026, les dépôts officiels publient actions/checkout v7.0.1, actions/setup-node v7.1.0, actions/upload-artifact v7.0.2 et actions/download-artifact v8.0.2, et les exemples du README de setup-node utilisent Node.js 24. --- Le même pipeline avec GitLab CI Chez GitLab, tout tient dans un fichier .gitlab-ci.yml à la racine, organisé en étapes (stages) plutôt qu'en dépendances entre jobs. stages: - test - build - deploy default: image: node:24 cache: key: files: - package-lock.json paths: - .npm/ test: stage: test script: - npm ci --cache .npm --prefer-offline - npm run lint - npm test rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH build: stage: build script: - npm ci --cache .npm --prefer-offline - npm run build artifacts: paths: - dist/ expire_in: 1 week rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH deploy_production: stage: deploy cache: [] script: - bash scripts/deploy.sh dist environment: name: production url: https://example.com resource_group: production rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual Trois différences comptent face à GitHub Actions, toutes vérifiées dans la référence YAML de GitLab CI/CD : - rules remplace only. La page des mots-clés dépréciés demande d'utiliser rules à la place de only et except. - when: manual crée un bouton. Le job de production attend qu'une personne le lance depuis l'interface : ce fichier s'arrête donc à la livraison continue. - resource_group joue le rôle de concurrency. Un seul job du groupe s'exécute à la fois, les autres attendent. Le cache suit l'exemple Node.js de la documentation GitLab sur le cache. Le job de déploiement, qui n'installe rien, le désactive avec cache: [], la forme indiquée par la page sur le cache. Les artefacts de build arrivent dans le job suivant sans étape de téléchargement. --- GitHub Actions ou GitLab CI : quotas gratuits et prix Sur l'offre gratuite, GitHub donne cinq fois plus de minutes que GitLab pour des dépôts privés, et ne compte rien pour un dépôt public sur ses runners standards. Le quota vaut pour tout le compte chez GitHub et pour tout l'espace de noms de premier niveau chez GitLab, pas par dépôt. Le tableau reprend les chiffres lus le 12 octobre 2026 sur la page de facturation de GitHub Actions, la page de prix de GitHub et la page de prix de GitLab. | Offre | Prix affiché | Minutes par mois | Stockage inclus | |---|---|---|---| | GitHub Free | 0 $ | 2 000 | 500 Mo d'artefacts | | GitHub Pro | Absent de la page lue | 3 000 | 1 Go d'artefacts | | GitHub Team | 4 $ par utilisateur et par mois | 3 000 | 2 Go d'artefacts | | GitLab Free | 0 $, 5 utilisateurs par groupe | 400 | 10 Gio | | GitLab Premium | 29 $ par utilisateur et par mois, facturé à l'année | 10 000 | 500 Gio | !Graphique en barres : minutes CI/CD incluses chaque mois pour automatiser ses déploiements, de 400 pour GitLab Free à 10 000 pour GitLab Premium, avec 2 000 pour GitHub Free et 3 000 pour GitHub Pro et Team Le prix de GitHub Team est affiché « pour les 12 premiers mois » (notre traduction). Le stockage ne se compare pas d'une ligne à l'autre : artefacts et paquets chez GitHub, dépôt Git et fichiers LFS chez GitLab. Le dépassement se paie différemment. GitHub facture à la minute : 0,006 $ sur Linux 2 cœurs, 0,010 $ sur Windows 2 cœurs et 0,062 $ sur macOS, selon sa grille des runners. GitLab vend des lots : 10 $ les 1 000 minutes, en paiement unique. Sa documentation sur les minutes de calcul précise que les minutes achetées et non utilisées sont reportées au mois suivant. !Courbe du coût du premier mois de dépassement de l'offre gratuite : 6 dollars chez GitHub et 30 dollars chez GitLab pour 3 000 minutes, 24 dollars contre 60 dollars pour 6 000 minutes > Notre calcul. Un compte consomme 3 000 minutes dans le mois sur ses dépôts privés. Sur GitHub Free, 1 000 minutes dépassent le quota : 1 000 × 0,006 $ = 6 $. Sur GitLab Free, 2 600 minutes dépassent : il faut trois lots, soit 30 $ le premier mois, dont 400 minutes reportées. Si ce rythme se répète, le report ramène la moyenne à 26 $ par mois (2 600 × 0,01 $) : les minutes achetées restent valables 12 mois, selon la page d'achat de GitLab. Ce calcul ignore le stockage. Trois règles changent la facture réelle : - L'arrondi par job. GitHub arrondit chaque job à la minute supérieure, selon sa grille des runners : notre pipeline consomme au moins trois minutes par push sur main, une par demande de fusion. - Le facteur de coût. GitLab multiplie la durée par un coefficient : 1 pour sa machine Linux par défaut, 2 pour la taille au-dessus. - Vos propres machines. À la date de lecture, ni GitHub (page de facturation) ni GitLab (page de prix) ne décomptent de minutes pour un job exécuté sur un runner que vous hébergez. Verdict : pour un projet personnel ou une petite équipe, l'offre gratuite de GitHub Actions est la plus large. GitLab Free devient vite juste, sauf à brancher son propre runner. --- Rolling, blue-green, canary : quelle stratégie choisir La stratégie décide de ce que voient vos utilisateurs pendant la mise à jour, et de la vitesse du retour arrière. La colonne « coût » du tableau est notre lecture, pas une mesure. | Stratégie | Principe | Retour arrière | Coût | |---|---|---|---| | Rolling | Les instances sont remplacées par petits groupes | Relancer l'ancienne version, progressivement | Faible | | Blue-green | Deux environnements identiques, un routeur bascule de l'un à l'autre | Rebasculer le routeur | Double infrastructure pendant la bascule | | Canary | La nouvelle version reçoit d'abord une petite part des utilisateurs | Rediriger tout le monde vers l'ancienne | Routage fin et supervision | Le rolling update est le comportement par défaut d'un Deployment Kubernetes. La documentation de Kubernetes fixe par défaut à 25 % la part de pods qui peuvent être indisponibles pendant la mise à jour, et à 25 % la part de pods créés en plus. Par défaut, un pod y est jugé disponible dès qu'il est prêt : une sonde de disponibilité (readiness probe) sert à dire quand l'application répond vraiment. Définissez-en une. Nos guides Kubernetes pour débutants et Docker pour débutants posent les bases. Le blue-green repose sur deux environnements de production aussi identiques que possible, dont un seul reçoit le trafic. Une fois la nouvelle version validée sur l'environnement inactif, le routeur bascule, et il rebascule en cas de problème (Martin Fowler, 1er mars 2010). Le canary ouvre la nouvelle version à un petit sous-ensemble d'utilisateurs avant de la généraliser (Danilo Sato sur le site de Martin Fowler, 25 juin 2014). Il suppose de savoir router une fraction du trafic et de surveiller des métriques. Dans les trois cas, deux versions cohabitent un moment. À notre lecture, le schéma de la base et les API doivent alors accepter l'ancienne et la nouvelle : c'est la difficulté que Fowler signale pour le blue-green. !Câbles réseau bleus et gris branchés sur un panneau de brassage numéroté Verdict : avec une seule instance, la priorité est un rollback rapide. À partir de deux, le rolling suffit à la plupart des petites équipes. Pour situer les fournisseurs, voyez notre comparatif AWS, Azure et Google Cloud. --- Secrets : ce qui ne doit jamais entrer dans le dépôt Un jeton de déploiement se range dans le coffre de la plateforme, jamais dans le code. Chez GitHub, un secret se crée dans les réglages du dépôt, puis se lit avec ${{ secrets.NOM }}, comme dans notre job deploy (documentation des secrets, consultée le 12 octobre 2026). Les règles à connaître, d'après cette page : - Les forks n'y ont pas accès. À l'exception de GITHUB_TOKEN, aucun secret n'est transmis à un workflow déclenché depuis un fork. Attention au déclencheur pull_request_target, qui peut accéder aux secrets selon la référence de sécurité. - Pas de secret dans un if. Il faut passer par une variable d'environnement. - Le masquage n'est pas garanti. GitHub masque les secrets dans les journaux, mais cette même référence prévient qu'une valeur transformée peut y échapper. Cette référence de sécurité rappelle aussi que toute personne ayant un accès en écriture au dépôt peut lire ses secrets, et recommande d'épingler les actions tierces sur l'empreinte complète d'un commit. L'approbation avant production a une limite. Un environnement GitHub peut exiger l'accord d'un relecteur. Mais la documentation des environnements précise que, sur les offres Free, Pro et Team, les relecteurs obligatoires ne sont disponibles que pour les dépôts publics. L'équivalent gratuit est la variante workflow_dispatch présentée plus haut. Chez GitLab, DEPLOY_TOKEN se crée dans Settings > CI/CD > Variables et arrive dans le job comme variable d'environnement. La documentation des variables indique que ces variables sont masquées par défaut depuis GitLab 18.3, et qu'une variable « protégée » n'est fournie qu'aux pipelines des branches et des tags protégés. Vers un fournisseur cloud, le mieux est de ne stocker aucun identifiant durable : avec OpenID Connect, le workflow obtient un jeton de courte durée, valable pour un seul job (documentation OIDC de GitHub). Le job doit alors déclarer id-token: write, précise la référence OIDC. Les permissions non citées dans un bloc tombent à none : gardez contents: read, sinon l'étape de checkout échoue sur un dépôt privé. deploy: permissions: contents: read id-token: write --- Rollback : préparer le retour arrière Un rollback fiable redéploie une version déjà validée, au lieu de corriger dans l'urgence. Quatre mécanismes existent. - Annuler le commit. Un git revert suivi d'un push relance tout le pipeline : sûr, mais au prix du temps complet des tests et du build. - Relancer une ancienne exécution GitHub. Une exécution peut être relancée jusqu'à 30 jours après son premier lancement, 50 fois au plus, et elle réutilise le commit d'origine (documentation de GitHub). Notre artefact expire au bout de 7 jours : passé ce délai, relancez l'exécution entière et pas seulement le job deploy. - Utiliser le bouton de GitLab. Dans la page de l'environnement, « Rollback environment » crée un nouveau déploiement pointant vers l'ancien commit. La documentation des déploiements GitLab précise que seuls les jobs de déploiement sont rejoués. Même limite que chez GitHub : passé expire_in, le job rejoué n'a plus d'artefact. Relancez alors le pipeline complet de l'ancien commit, ou allongez expire_in. - Revenir en arrière dans Kubernetes. La commande kubectl rollout undo restaure la révision précédente. Kubernetes en conserve 10 par défaut, d'après la documentation des Deployments. Aucun de ces mécanismes ne défait une migration de base de données. La parade décrite par Martin Fowler : modifier d'abord le schéma pour qu'il accepte l'ancienne et la nouvelle version du code, déployer, puis nettoyer plus tard. Verdict : pour automatiser ses déploiements sereinement, écrivez la procédure de retour arrière en même temps que le pipeline, et alignez la conservation des artefacts sur le délai pendant lequel vous voulez pouvoir revenir. --- Ce que mesure la recherche DORA À notre lecture, automatiser ne suffit pas à livrer mieux. Le guide des métriques DORA affirme que vitesse et stabilité ne s'opposent pas, mais les rapports DORA 2024 et 2025 associent l'adoption de l'IA à une stabilité en baisse. Le rapport 2024 estime qu'à mesure que l'adoption de l'IA progressait, le débit de livraison reculait de 1,5 % et la stabilité de 7,2 % (Google Cloud, synthèse du rapport DORA 2024, 23 octobre 2024). Le rapport 2025, fondé sur les réponses de près de 5 000 professionnels, constate un lien positif entre l'IA et le débit de livraison, mais toujours un lien négatif avec la stabilité (Google Cloud, annonce du rapport DORA 2025, 23 septembre 2025). L'explication avancée dans l'annonce du rapport 2025 concerne votre pipeline : sans tests automatisés solides et sans retours rapides, l'accélération se traduit en instabilité. Si vous cherchez à coder plus vite avec l'IA, le pipeline est, selon nous, le filet de sécurité. Ces chiffres sont des corrélations issues d'enquêtes. Par où commencer : 1. le job test sur chaque demande de fusion ; 2. le build et l'artefact ; 3. le déploiement, d'abord vers une recette ; 4. un rollback répété un jour calme. --- Ce que nous n'avons pas pu vérifier Plusieurs points restent hors de portée ou incertains : - L'exécution des deux fichiers YAML. Chaque mot-clé a été relu dans la documentation, mais aucun pipeline n'a été lancé. Validez-les sur une branche. - Les seuils de performance DORA. Ils figurent dans les rapports complets, que nous n'avons pas pu relire : ils sont absents de cet article. - Le prix de GitHub Pro et les prix en euros. La page de prix lue ne présente que les offres Free, Team et Enterprise, en dollars. - Les conséquences d'un quota GitLab épuisé. La page lue parle de « mesures de restriction » (enforcement) sans les détailler. --- Questions fréquentes Quelle est la différence entre livraison continue et déploiement continu ? En livraison continue, une personne déclenche la mise en production, par exemple avec un job when: manual chez GitLab. En déploiement continu, elle part seule dès que les tests passent, ce qui suppose des tests fiables et un rollback rapide. GitHub Actions est-il gratuit ? Oui pour les dépôts publics, sur les runners standards. Pour les dépôts privés d'un même compte, la page de facturation de GitHub indique 2 000 minutes par mois sur l'offre Free, chiffre lu le 12 octobre 2026. Au-delà, le runner Linux 2 cœurs coûte 0,006 $ la minute. Faut-il choisir GitHub Actions ou GitLab CI ? Choisissez celui de la plateforme qui héberge déjà votre code. En offre gratuite, GitHub inclut 2 000 minutes par compte contre 400 par espace de noms pour GitLab, sur des dépôts privés. GitLab garde un avantage : un bouton d'approbation intégré au pipeline, sans offre payante. Chez GitHub, l'équivalent gratuit est la variante workflow_dispatch du même fichier, lancée par le bouton « Run workflow ». Comment annuler un déploiement raté ? Redéployez la dernière version saine : git revert puis push, relance d'une ancienne exécution GitHub dans la limite de 30 jours, bouton « Rollback environment » de GitLab, ou kubectl rollout undo sur Kubernetes. --- À propos de l'auteur Abdelilah Ourti est ingénieur IA et architecte logiciel. Il conçoit et déploie des applications web et des systèmes d'IA, et publie sur NeurauTech des guides et des comparatifs destinés aux développeurs francophones. Ses autres articles sont réunis sur sa page auteur. Méthode. Synthèse de recherche fondée exclusivement sur des sources publiques : documentation et pages de prix de GitHub et de GitLab, documentation de Kubernetes, site de Martin Fowler, guide et rapports du programme DORA. Les deux fichiers YAML ont été relus contre la documentation officielle mais n'ont pas été exécutés. Les citations issues de sources en anglais sont traduites par nos soins. Aucun lien n'est affilié et aucune relation commerciale ne lie l'auteur aux éditeurs mentionnés. Une erreur ou un prix périmé ? Écrivez à contact@neurautech.com : l'article sera corrigé et la date de mise à jour modifiée. Dernière mise à jour : 12 octobre 2026. Images : Domaintechnik et Jordan Harrison (Unsplash License).

  5. Setting Up a Fitness App Project: Repository Structure, CI, and Documentation !Image description The first workout screen works. Users can record a session, and the API saves it. Then another developer joins. They cannot identify the required environment variables. The backend starts only after an undocumented database change. A pull request breaks a calculation, but nobody notices until someone tests the screen manually. The project needs a repeatable way to understand, verify, and change its behavior. A maintainable fitness app project setup connects clear component boundaries, automated checks, and documentation another developer can follow. This guide uses a hypothetical fitness application, not a real production system. It contains a mobile client and a Node.js API, with an optional administration interface. Define the Architecture Before Creating Folders Identify what runs, what stores data, and what communicates across a boundary. For the hypothetical application: - The mobile client presents workouts and records input. - The backend API authenticates requests and manages application data. - The database stores users, exercises, and workout records. - An optional administration interface manages approved content. - Shared schemas describe selected request and response formats. - Tests and development tools verify the components. A database layer can remain part of the API. A shared package should exist because multiple components need a maintained contract, not simply because monorepos often contain one. Monorepo or Separate Repositories? A monorepo keeps related components together and can make coordinated API, schema, and client changes easier to review. Separate repositories can suit independent ownership, access restrictions, or release processes. | Consideration | Monorepo | Separate repositories | |---|---|---| | Coordinated changes | One pull request can cover related changes | Changes need cross-repository coordination | | Access boundaries | Usually broader repository visibility | Access can be isolated by component | | Shared tooling | Central configuration is possible | Configuration may need distribution | | Releases | Require component-specific conventions | Separate releases still need compatibility planning | Choose according to actual coordination and ownership needs. For framework planning, see React Native vs Flutter for fitness app development. Document the approach actually selected and its platform-specific requirements. Design a Practical Repository Structure This directory map is illustrative, not a mandatory template. | Path | Responsibility | |---|---| | apps/mobile/ | Mobile application | | apps/admin/ | Optional administration interface | | services/api/ | Backend and data-access layer | | services/api/src/ | API implementation | | services/api/tests/ | API unit and integration tests | | services/api/migrations/ | Database migrations when required | | packages/shared/ | Shared contracts when justified | | docs/ | Architecture, setup, API, and deployment documentation | | scripts/ | Maintained development utilities | | tests/e2e/ | Cross-component user-journey tests | | .github/workflows/ | GitHub Actions workflows | | .github/PULL_REQUEST_TEMPLATE.md | Pull request prompts | | .gitignore | Excluded local and generated files | | .editorconfig | Basic editor conventions | | README.md | Project entry point | | CONTRIBUTING.md | Collaboration instructions | The API in this example has its own package manifest and lockfile. The repository does not use npm workspaces. A smaller project can omit the admin application, shared package, and cross-component test directory. Choose license terms appropriate to the project's intended distribution. Do not automatically apply an open-source license to proprietary code. Organize Code Around Responsibilities For workout logging, separate presentation, application logic, business rules, data access, and authorization. A screen should not independently implement access policy. The backend must enforce access to protected data. Workout calculations should not depend on UI components. A duration function should receive defined inputs and return a result without requiring a network connection. This makes empty records, invalid values, and unit conventions easier to test. Avoid creating interfaces and factories for every small function. Introduce abstraction when it manages an existing dependency or variation. For related domain planning, see fitness tracking app development. Establish Git and Collaboration Conventions Short-lived branches and pull requests may provide enough structure for a small team. Document branch creation, review responsibilities, required checks, release identification, and urgent-fix procedures. Commit messages should describe the change: Add workout history filtering by date Reject negative exercise duration values Fix duplicate workout import after retry Document local database migration setup If using Conventional Commits, document the rules and any tooling that depends on them. Make Pull Requests Easier to Review A useful description answers: 1. What behavior changed? 2. Why was it needed? 3. How was it verified? 4. Does it require a migration or configuration change? 5. Which documentation changed? Configure appropriate branch protection or rulesets where available. Record exact required check names: renaming a workflow check can affect merging. Commit applicable lockfiles and review dependency changes with the application changes. Configure Continuous Integration Around Real Checks Continuous integration runs verification when changes are proposed or integrated. Suitable checks include dependency installation, formatting, linting, type checking, unit tests, integration tests, builds, and security checks. Each check should answer a question. A build verifies compilation; it does not prove that a workout is saved correctly. CI differs from deployment. Successful checks do not require publishing an app or changing production. Start with a small pipeline that reliably runs meaningful checks. Extend it when additional risks justify the work. A Minimal GitHub Actions Workflow This workflow checks only the hypothetical Node.js API. Assumptions: - The API lives in services/api/. - It has its own committed package-lock.json. - It does not depend on an unconfigured local shared package. - Node.js 24 is compatible with its dependencies. - The package defines lint, test, and build scripts. - Tests run once and exit. - Production credentials are unnecessary. Node.js 24 is a supported LTS line at the time of writing. Choose a supported version compatible with your project and align local development with CI. Reference: Node.js release information. Create .github/workflows/ci.yml: name: API CI on: pull_request: push: branches: - main permissions: contents: read jobs: api-checks: name: API checks runs-on: ubuntu-latest timeout-minutes: 15 defaults: run: working-directory: services/api steps: - name: Check out repository uses: actions/checkout@v7 with: persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v7 with: node-version: "24" package-manager-cache: false - name: Install dependencies run: npm ci - name: Run lint checks run: npm run lint - name: Run tests run: npm test - name: Validate build run: npm run build The workflow uses read-only repository permissions and disables persisted checkout credentials. It has no deployment step. Consult the official checkout and setup-node documentation for runner compatibility. For stronger control over updates, use reviewed full commit SHA pins and maintain an update process. Configure the Package Scripts First For a TypeScript API using ESLint and Vitest, the relevant package manifest excerpt could be: { "private": true, "scripts": { "lint": "eslint .", "test": "vitest run", "build": "tsc -p tsconfig.json" } } This is not a complete manifest. It requires installed development dependencies, ESLint configuration, TypeScript configuration, and actual tests. Do not hide missing required scripts with optional execution flags. The npm ci command requires an existing lockfile and fails if it does not match the manifest. Keep installation settings consistent with those used to create the lockfile. Reference: npm ci documentation. Add Mobile Validation Separately Passing API checks does not verify the mobile application. Add checks appropriate to the actual framework and supported platforms. iOS build jobs generally need suitable macOS infrastructure and the relevant Apple toolchain. Keep signed releases separate from ordinary pull request checks. Handle Environment Configuration Safely Document configuration separately for each component and environment. An API example configuration file could contain: DATABASE_URL= AUTH_ISSUER_URL= PORT=3000 The mobile component might use: API_BASE_URL=http://localhost:3000 These examples contain no credentials. Explain how configuration is loaded and validated; an example file does not implement environment loading. For emulators and physical devices, document how to reach the development API. Localhost refers to the device or machine interpreting the address. Exclude Secrets and Generated Files An illustrative root ignore file could include: **/node_modules/ **/dist/ **/coverage/ .env .env.* **/.env **/.env.* !.env.example !**/.env.example Adapt generated-file exclusions to the framework. Ignore rules do not remove files already tracked or erase history. If a credential is exposed, rotate or revoke it, review its use, and then address repository exposure. Use restricted CI secrets only when necessary. Ordinary checks should use isolated test resources. Mobile application configuration is not a safe place for privileged server credentials. Write Documentation Developers Can Use Documentation should enable tasks without duplicating information reliably maintained by tooling. | Document | Purpose | |---|---| | README.md | Purpose, components, prerequisites, quick start, and checks | | docs/architecture.md | Boundaries, data flows, and design decisions | | docs/development-setup.md | Configuration, database setup, migrations, and synthetic data | | docs/api-overview.md | Authentication, errors, and maintained API contracts | | docs/deployment.md | Releases, migrations, monitoring, rollback, and recovery | | CONTRIBUTING.md | Branches, reviews, dependencies, and documentation expectations | A README outline: # Project Name ## Purpose ## Components ## Prerequisites ## Local Setup ## Configuration ## Database Setup ## Running the Application ## Running Checks ## Troubleshooting ## Contribution Workflow ## Deployment Documentation Add verified commands and state where they run. Link to maintained API schemas rather than duplicating endpoint lists. Update documentation in the same pull request as the relevant change. For agency-built projects, also document repository administration and handover responsibilities. Related reading: fitness app development contracts: code and data ownership. Document Fitness Data and Failure Behavior Define identifiers, ownership, exercises, sets, repetitions, weights, units, duration, timestamps, completion, edits, and offline behavior. Explain which records contribute to progress calculations. Record why each data field is collected and who can access it. Related reading: fitness app data collection checklist. Treat Integrations as Optional Components Not every app needs HealthKit, Health Connect, or wearables. When included, document permissions, supported data types, synchronization direction, provider limits, and disconnection. Explain duplicate prevention, retries, missing information, expired authorization, delayed records, and partial imports. Health Connect recommends aggregation for cumulative records such as steps to reduce double counting. Verify behavior for each record type and provider. Reference: Health Connect data reading guidance. Related reading: - Fitness app development with wearable integration - Wearable workout data handling Document Recommendation Logic If Present Record inputs, constraints, model or provider versions, evaluation procedures, and fallbacks. Rules and learned recommendations have different maintenance needs. Related reading: rules-based vs machine learning personalization. Only include AI infrastructure when the product needs it. Build Tests Around Important Decisions | Level | Fitness-app example | |---|---| | Unit | Validate duration calculations and invalid inputs | | Integration | Save and retrieve a workout through the API and database | | End-to-end | Sign in, complete a workout, and reopen its history | | Build | Compile supported application components | | Regression | Reproduce a previously fixed duplicate-import defect | Integration tests should use an isolated database with expected migrations. Never point them at production. Use synthetic data and deterministic inputs. Test denied access as well as valid access, including whether one user can read another user's private workouts. More expensive suites can run at appropriate stages, provided release-critical validation happens before release. Plan Dependency and Access Maintenance Record supported runtimes and check compatibility before upgrading. Review dependency updates for purpose, maintenance, licensing, and operational implications. Apply least privilege to repository members and CI identities. Restrict production records and release credentials. Document upgrades, migration recovery, and incident responsibilities. Automated vulnerability checks do not establish complete security or legal compliance. Privacy obligations depend on the product, data, jurisdictions, and agreements. For related planning considerations, see fitness app HIPAA compliance and GDPR. Common Setup Mistakes | Mistake | Prevention | |---|---| | Unstructured code | Define component responsibilities | | Premature repositories and abstractions | Add boundaries when justified | | Missing lockfiles | Commit and review applicable lockfiles | | Unusable README | Verify a clean setup | | CI invokes nonexistent scripts | Run every command locally first | | Secrets are committed | Use restricted configuration and review changes | | Builds are treated as correctness proof | Test behavior and integrations | | Migrations are ignored | Version them and test a fresh database | | Only successful integrations are documented | Explain retries, missing data, and denied access | | Passing CI deploys production without controls | Separate verification and release operations | Project Setup Checklist Repository and Architecture - [ ] Identify components and ownership. - [ ] Choose repository boundaries based on actual needs. - [ ] Document important data flows. - [ ] Remove unnecessary abstractions. Git and Collaboration - [ ] Define branches, reviews, and release conventions. - [ ] Add a useful pull request template. - [ ] Configure appropriate required checks. - [ ] Document urgent-fix procedures. Dependencies and Environment - [ ] Record supported runtimes and toolchains. - [ ] Commit manifests and lockfiles. - [ ] Provide safe configuration examples. - [ ] Document database setup and migrations. CI and Testing - [ ] Verify every CI script. - [ ] Run checks on pull requests and main-branch pushes. - [ ] Test business logic and integrations. - [ ] Validate supported mobile platforms. - [ ] Isolate tests from production. Documentation - [ ] Add a verified quick start. - [ ] Explain architecture, APIs, and configuration. - [ ] Document common setup failures. - [ ] Update documentation with relevant changes. Security and Access - [ ] Exclude secrets and generated files. - [ ] Review tracked files for sensitive information. - [ ] Apply least-privilege repository and CI permissions. - [ ] Define credential rotation and vulnerability handling. Fitness Integrations - [ ] Document units and record identity. - [ ] Record applicable permissions and consent. - [ ] Test duplicate records and synchronization failures. - [ ] Explain external dependencies and fallbacks. Deployment and Maintenance - [ ] Separate CI from releases. - [ ] Document deployment and migration procedures. - [ ] Define monitoring, rollback, and recovery. - [ ] Assign maintenance responsibilities. Verify the Foundation With a Fresh Setup Repository structure makes responsibilities visible. CI verifies selected behavior. Documentation explains how to work with the system. Keep all three aligned with current requirements. Ask another developer to clone the repository, follow the instructions, run the checks, and explain the main components. Fix the points where that process breaks.

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

Scan pending.

README badges

Embed a badge in your README or website.

Live mentions · last 30 days
actions/setup-node mentions in the last 30 days