Skip to content

🐛 fix(deploy): un push de release sur main laisse la prod au tag - #140

Open
kbrdn1 wants to merge 5 commits into
devfrom
fix/#139-release-owns-prod-deploy
Open

kbrdn1 wants to merge 5 commits into
devfrom
fix/#139-release-owns-prod-deploy

Conversation

@kbrdn1

@kbrdn1 kbrdn1 commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Une release déployait la prod deux fois : le merge dev → main réveillait deploy.yml (le filet) avec :main, puis le tag réveillait release.yml avec :vX.Y.Z. Sur la v2.1.0, ça a donné deux swaps nginx à une minute d'intervalle (22:23:36 puis 22:24:39 UTC). Le premier swap a eu lieu avant les gates de release.yml. Les deux builds du même commit n'avaient pas les mêmes chunks (12 de différence).

Désormais, un push sur main qui monte la version de package.json construit toujours :main, mais ne déploie plus : la prod appartient au tag. Les pushes sans bump gardent le filet, comme #32. Une version qui redescend le garde aussi : le revert d'une release n'a aucun tag à venir. Dans le doute, on déploie comme avant (fail open).

Related Issue

Closes #139

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • ♻️ Refactoring (no functional changes)
  • ⚡️ Performance improvement
  • 🎨 Style/UI update

Scope

Which parts are affected?

  • Homepage
  • Blog
  • Projects
  • UI Components
  • i18n / Translations
  • SEO
  • Configuration

Changes

  • scripts/resolve-main-push.sh <before> <after> émet deploy=true|false, from, to au format de $GITHUB_OUTPUT. La décision se prend sur une montée stricte de .version de package.json (sort -V), et non sur « package.json a changé ». Un bump de dépendance ou un revert ne sautent donc pas le déploiement. Un before nul, vide ou introuvable, ou une version absente, donne deploy=true.
  • deploy.yml :
    • le job resolve lance ce script uniquement sur push vers refs/heads/main. Le chemin dev → preprod, les workflow_dispatch et le rollback ne changent pas ;
    • le job deploy exige deploy != 'false' ;
    • un saut laisse une ligne dans le résumé du run (« Deploy prod laissé au tag »).
  • scripts/resolve-main-push.test.sh construit un dépôt git jetable et couvre 12 cas. Il est lancé dans les Script checks de ci.yml.
  • Docs :
    • docs/RELEASE.md : vue d'ensemble, concurrence, prod qui reste sur l'ancienne version si le job du tag échoue, /api/health ;
    • en-tête de release.yml ;
    • CLAUDE.md ;
    • entrée Fixed sous [Unreleased].

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have tested my changes locally (bash scripts/resolve-main-push.test.sh : 12/12 ; une mutation qui inverse la décision le fait échouer)
  • My changes generate no new warnings (actionlint : 8 findings sur dev, 8 sur la branche, tous dans des steps non touchés ; shellcheck propre sur les deux scripts)
  • I have updated the documentation if needed
  • TypeScript compiles without errors (make typecheck) : pas lancé en local, aucun fichier TS ou Vue touché. La CI le fait.
  • Linting passes (make lint) : bun run lint sous nix develop (Bun 1.4.4), exit 0, 2 warnings préexistants dans des .vue
  • Formatting passes (bun run fmt:check)

Additional Notes

Rejeu sur le vrai historique de main (before = premier parent du merge) :

bd5ee7e deploy=false from=2.0.0 to=2.1.0   Merge pull request #136 from kbrdn1/dev
11fc1be deploy=false from=1.1.0 to=2.0.0   Merge pull request #126 from kbrdn1/dev
68a49e4 deploy=false from=1.0.2 to=1.1.0   Merge pull request #60 from kbrdn1/dev
958bb73 deploy=false from=1.0.1 to=1.0.2   Merge pull request #50 from kbrdn1/dev
8f65002 deploy=false from=1.0.0 to=1.0.1   Merge pull request #41 from kbrdn1/dev
3ef37f9 deploy=true  from=1.0.0 to=1.0.0   Merge pull request #32 from kbrdn1/dev
1e512ce deploy=true  from=      to=1.0.0   Merge pull request #29 from kbrdn1/dev

Un choix de design à valider. La règle pouvait aussi être « plus de prod depuis deploy.yml du tout ». Je l'ai écartée : #32 montre qu'un push sur main sans bump arrive, et il n'avait que le filet.

Changement de comportement. Si le job Deploy prod du tag échoue, la prod reste sur la version précédente. Avant, le filet y avait déjà mis le code de la release, comme sur la v1.0.1 où /api/health disait 1.0.1 alors que Deploy prod avait échoué. Rattrapage : gh run rerun <run> --failed, déjà documenté.

Cas limite connu (P3 de la review, non corrigé). Réappliquer une release revertée fait remonter la version vers un tag qui existe déjà, ce qui donne deploy=false. Aucun tag ne viendra, et la prod reste sur le code reverté. L'échec se fait du côté sûr : rien de non validé ne part. Rattrapage : gh workflow run release.yml -f tag=vX.Y.Z. Tester l'existence du tag dans le script rouvrirait la course de #139 : un tag poussé quelques secondes après le merge ferait repasser deploy à true.

Non vérifiable avant le merge. Le chemin push main ne tourne qu'au prochain merge de release. Ce sera le premier vrai test : le run deploy.yml doit montrer Build & push image vert, Deploy prod on VPS sauté et la ligne de résumé. Ce merge-ci, vers dev, exercera le chemin preprod, qui ne doit pas bouger.

Hors scope (détaillé dans #139) :

  • même double déploiement en préprod (push dev puis tag rc) ;
  • le build non reproductible : course dans @nuxtjs/mdc, l'ordre du CSS shiki suit l'ordre de fin des promesses, non corrigé en amont en 0.23.2 ;
  • les 404 de chunks propres à tout déploiement : chaque swap retire les chunks du build précédent.

Le merge dev → main déployait :main en prod, puis le tag déployait
:vX.Y.Z une minute plus tard : deux swaps pour une release, le premier
avant les gates de release.yml, et deux builds du même commit dont les
chunks diffèrent (12 sur la v2.1.0).

Un push sur main qui change la version de package.json construit
toujours :main, mais ne déploie plus : la prod appartient au tag. Les
pushes sans bump gardent le filet, et dans le doute (before nul ou
introuvable, version absente) on déploie comme avant. La règle vit dans
scripts/resolve-main-push.sh pour être rejouable hors CI.

refs #139
Dépôt git jetable : un bump laisse la prod au tag, un push sans bump ou
un bump de dépendance seul garde le filet, et chaque cas douteux (before
nul, vide, introuvable, version absente) déploie. Une erreur dans un sens
rejoue le double déploiement, dans l'autre elle coupe le filet.

Lancé dans les Script checks de ci.yml, à côté de resolve-release-tag.

refs #139
RELEASE.md : vue d'ensemble (le filet saute les pushes qui bumpent), la
phrase « les deux ne se réveillent jamais ensemble », déjà fausse, la
concurrence qui sert encore à d'autres chevauchements, la prod qui reste
sur l'ancienne version si le job du tag échoue, et le « ne prouve pas »
de /api/health.

Même chose dans l'en-tête de release.yml et dans CLAUDE.md, et une
entrée Fixed sous [Unreleased].

closes #139
@kbrdn1 kbrdn1 added the fix Bug fix label Nov 2, 2026
`FROM != TO` traitait le revert d'une release (2.1.0 → 2.0.0) comme un
bump : le filet sautait alors qu'aucun tag ne suit, le sien est déjà
publié, et la prod restait sur la release fautive. Seule une montée de
version laisse désormais la prod au tag, comparée avec `sort -V` pour
que 1.10.0 passe devant 1.9.0.

Trouvé par la review (axe standards). Test : revert et 1.10.0 → 1.9.0
gardent le filet, 1.9.0 → 1.10.0 le laisse au tag.

refs #139
)

Le commentaire de concurrence du job deploy de release.yml justifiait
encore la clé partagée par le double déploiement que #139 supprime ; il
cite maintenant les chevauchements qui restent. RELEASE.md et CLAUDE.md
disent « monte la version » et couvrent le revert, qui garde le filet.

refs #139

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant