Skip to content

fix: stop treating the floating major tag as a GitHub release#17

Merged
phramz merged 1 commit into
mainfrom
fix/major-tag-release-hijacking
Apr 17, 2026
Merged

fix: stop treating the floating major tag as a GitHub release#17
phramz merged 1 commit into
mainfrom
fix/major-tag-release-hijacking

Conversation

@phramz

@phramz phramz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

After PR #15 + v1.1.1, @v1 consumers were still hitting the stale binary. Root cause: a standalone v1 GitHub Release (uploaded when PR #10 out-of-band force-pushed the v1 git tag and triggered the old release-action.yml) hijacks resolveReleaseTag — it finds /releases/tags/v1 → 200 → returns v1 → downloads the v1.0.1-era binary without PR #14's coverage-driver install. PHPUnit then reports "No code coverage driver available / No tests executed".

The intent of the existing resolver is already that @v1 resolves via /releases/latest + major-match (see resolveReleaseTag falls back to latest for floating major tag test). The bug is that nothing enforced it. This PR encodes the contract in code, workflow, and docs.

Architectural contract (now codified)

The v<MAJOR> git tag is a git-ref convenience for uses: owner/repo@v1 checkout only. It MUST NOT have a corresponding GitHub Release. Only specific-version tags (vX.Y.Z) get releases with assets.

Changes

  • src/index.js:resolveReleaseTag: floating-major refs (/^v\d+$/) skip /releases/tags/<ref> entirely and resolve via /releases/latest + major-match. Specific-version refs (/^v\d+\.\d+\.\d+/) that 404 on lookup now throw instead of silently substituting latest (pre-existing bug: violated pin reproducibility).
  • .github/workflows/release-please.yml: new Ensure no standalone major-tag release exists step after the major-tag move — belt-and-braces cleanup if a v1 release ever appears (shouldn't post-PR fix: build and upload release assets from release-please workflow #15, but cheap insurance).
  • test/unit/index.test.js: 1 existing test strengthened (proves resolver doesn't call /releases/tags/ for floating major), 2 new tests — ignores a standalone floating-major release (direct regression test for today's incident) and rejects when a specific-version release is missing.
  • CLAUDE.md: new Release Engineering section documenting the contract.

Post-merge cleanup (out of band, with your OK)

gh release delete v1      --yes --cleanup-tag=false
gh release delete v1.0.1  --yes --cleanup-tag=false
gh release delete v1.1.0  --yes --cleanup-tag=false

Git tags preserved in all three cases. @v1 then resolves via latest-major-match to v1.1.1.

Test plan

  • npm test — 15/15 pass
  • make check — green
  • actionlint — clean
  • After merge: delete v1, v1.0.1, v1.1.0 GitHub releases
  • After cleanup: re-run failing consumer workflow → green

A standalone `v1` GitHub Release (uploaded out-of-band when PR #10
force-pushed the v1 git tag and triggered the old release-action.yml)
was still pinning `@v1` consumers to stale v1.0.1-era assets: the
resolver found /releases/tags/v1 -> 200 and returned `v1`, downloading
the pre-coverage-install binary that made PHPUnit report "No code
coverage driver available / No tests executed".

Codify the contract that the vN git tag is a `uses:` convenience and
must never have a matching release:

- src/index.js:resolveReleaseTag skips /releases/tags/<ref> for
  floating-major refs (/^v\d+$/), resolving through /releases/latest +
  major-match. Also propagates 404 as an error for specific-version
  pins (/^v\d+\.\d+\.\d+/) instead of silently substituting latest
  (which violated pin reproducibility).
- release-please.yml adds a defense-in-depth step that deletes any
  standalone vN release after each release cut.
- Unit tests cover the regression directly (ignores a standalone
  floating-major release) plus the pin-respect tightening.
- CLAUDE.md documents the release-artifact contract.

Post-merge cleanup (out of band): `gh release delete v1 v1.0.1 v1.1.0`
to purge the stale + empty-asset releases while keeping the git tags.
@phramz
phramz merged commit f04e1e4 into main Apr 17, 2026
10 checks passed
@phramz
phramz deleted the fix/major-tag-release-hijacking branch April 17, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant