Skip to content

Tag releases at the commit that carries the version - #187

Merged
wr merged 4 commits into
mainfrom
wells/release-script-tag-fix
Aug 4, 2026
Merged

Tag releases at the commit that carries the version#187
wr merged 4 commits into
mainfrom
wells/release-script-tag-fix

Conversation

@wr

@wr wr commented Aug 4, 2026

Copy link
Copy Markdown
Owner

release.sh bumps MARKETING_VERSION and CURRENT_PROJECT_VERSION, then never commits them. It only commits to gh-pages. So every tag points at source declaring the previous version:

tag project.yml at that tag
v1.9.0 1.8.1
v1.8.1 1.8.0
v1.8.0 1.7.0

Checking out a release tag doesn't rebuild that release. The bump has been landing as a manual Release vX.Y.Z commit afterwards, one commit too late.

Change

Commit the bump inside the script and tag the release at that commit (gh release create --target).

Placement is deliberate: the commit happens after build, notarize, staple and sign — the steps that actually fail — so a run that dies partway leaves no stray commit behind. Everything before that point is reversible.

Preflight

Committing from the script means the working tree has to be trustworthy, so it now refuses to start unless:

  • you're on main — not a feature branch
  • the tree is clean — otherwise the bump commit sweeps up unrelated work
  • main is in sync with origin — checked up front so the push at the end can't fail after the release is already public

All three run before the ~4-minute build, alongside the existing changelog and SUPublicEDKey checks.

Test plan

Each guard exercised against the real script, not a copy:

condition result
feature branch error: on branch 'wells/release-script-tag-fix', expected 'main'.
uncommitted changes error: working tree is dirty. Commit or stash first: + git status --short
1 commit unpushed error: … is out of sync with origin (1 ahead, 0 behind). Pull/push first.
clean and in sync proceeds past preflight

bash -n clean. The commit/tag path itself runs only during a real release — it'll get its first live exercise on v1.9.1.

Not fixed

The existing v1.8.0 / v1.8.1 / v1.9.0 tags stay where they are. Moving a published tag rewrites history users may already have fetched.


Review round 2

Adversarial review found that the first commit made re-runs worse than before, plus a repo-identity hole. Both fixed.

A retry could publish a junk commit. The version bump is unconditional, so re-running an already-released version burned a build number and pushed a public Release vX.Y.Z commit — then died at gh release create on the tag collision. That's a regression the tagging change introduced, and it lands on main where everyone sees it.

Now checked before anything is pushed, as two separate guards:

  • the tag exists on origin
  • a release exists on $GITHUB_REPO — a partly-failed gh release create can leave a draft behind without ever creating the tag, which a tag check alone misses

Origin might not be the release repo. The bump pushes to origin while the release is cut against $GITHUB_REPO. If those differ (a fork, or a stale .env), every other guard passes and --target then references a SHA the release repo doesn't have. Now asserted up front.

Verified against the real script: v1.9.0error: tag v1.9.0 already exists on origin.

Review also confirmed as sound: detached-HEAD handling, set -euo pipefail interaction, the AHEAD/BEHIND arithmetic, --target accepting a just-pushed SHA, and that a rejected push leaves only a local commit with nothing published.

release.sh bumped MARKETING_VERSION and CURRENT_PROJECT_VERSION but never
committed them, so every tag pointed at source declaring the *previous*
version — v1.8.1's tree says 1.8.0, v1.9.0's says 1.8.1. Checking out a
tag didn't rebuild that release.

Commit the bump and tag at it. The commit lands after the build, notarize
and sign steps rather than before, so a run that fails partway leaves no
stray commit behind.

Guard the whole thing on a clean, in-sync main: without that the bump
commit would sweep up unrelated working-tree changes, and a push failing
at the end would strand an already-public release.

@wr-claude-reviewer wr-claude-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. The fix is correct and the preflight guards are well-designed.

What's solid:

  • git status --porcelain / ahead-behind check are robust preflight guards; failure messages are clear and tested.
  • Placing the commit after build/notarize/staple/sign is the right call — those are the steps that actually fail, so a bad run leaves no orphan commit on main.
  • Capturing RELEASE_SHA after the push and threading it into gh release create --target is the right way to pin the tag; using a branch name there would be racy.
  • fetch --quiet before the sync check means the behind-count reflects the actual remote state, not a stale local tracking ref.

CI note: only the ssot review workflow is showing (in-progress); no build/test CI run visible for this diff, consistent with this being a shell-script-only change with no Swift sources touched.

@wr
wr force-pushed the wells/release-script-tag-fix branch from 219af70 to eb70129 Compare August 4, 2026 21:46
wr added 3 commits August 4, 2026 17:47
A re-run was worse than before the tag change: the version bump is
unconditional, so retrying an already-released version burned a build
number and pushed a public "Release vX.Y.Z" commit before dying at
`gh release create`. Check for the tag, and separately for a release —
a partly-failed `gh release create` can leave a draft with no tag.

Also assert origin is $GITHUB_REPO. The bump pushes to origin while the
release is cut against $GITHUB_REPO; if they differ, every other guard
passes and --target then references a SHA the release repo doesn't have.
The previous commit was scratch state from exercising the branch guard
against this branch instead of main.
@wr
wr merged commit f825d60 into main Aug 4, 2026
5 checks passed
@wr
wr deleted the wells/release-script-tag-fix branch August 4, 2026 21:49

@wr-claude-reviewer wr-claude-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 follow-up — previous findings addressed.

The two concerns raised after the first approval are now correctly handled:

Re-release guard: ls-remote --exit-code --tags catches a published tag; the separate gh release view catches a draft left by a partly-failed run. Both run before any push — ordering is correct.

Origin/GITHUB_REPO identity check: glob-matching $ORIGIN_URL against $GITHUB_REPO catches forks and stale .env configs up front, before the branch is touched.

Failed-push failure mode: If git push fails (step 5), set -euo pipefail exits immediately with a local-only "Release vX.Y.Z" commit. On re-run the ahead-behind guard (AHEAD != 0) blocks cleanly, requiring a manual reset — correct and safe.

RELEASE_BRANCH correctly restored to "main".

CI: only the ssot review workflow is visible; no build/test CI is expected for a shell-script-only change.

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