Skip to content

chore(release): decouple CHANGELOG/tagging from starting a build - #166

Merged
keinstn merged 1 commit into
mainfrom
chore/split-release-recipe
Aug 9, 2026
Merged

chore(release): decouple CHANGELOG/tagging from starting a build#166
keinstn merged 1 commit into
mainfrom
chore/split-release-recipe

Conversation

@keinstn

@keinstn keinstn commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • just release <semver> used to bump the version, generate the CHANGELOG entry, tag, and start an Xcode Cloud build all in one step. But App Store review can reject or delay a build for days, during which main keeps moving — the tag and changelog range ended up pinned to whatever HEAD happened to be at release time, not to what actually shipped.
  • release now only bumps app/pubspec.yaml and starts a build; it no longer touches CHANGELOG.md or tags.
  • New just tag-release <semver> records the changelog entry and git tag, meant to run once App Store Connect confirms the version is actually live (READY_FOR_DISTRIBUTION).
  • tag-release resolves the commit that actually shipped via Xcode Cloud's own build-run records — matching the App Store version's attached build (asc versions view --include-build), not just the newest upload under that marketing version, since those can differ if a build-only just release runs again after submission. It does not trust app/pubspec.yaml's build number for this: build-run history shows Xcode Cloud assigns CFBundleVersion from its own run counter, which doesn't reliably match what was last bumped in pubspec (run 48 and run 49 both built the identical commit and produced two different ASC build numbers).
  • Fixes ci_post_clone.sh's comment, which asserted the opposite (that CFBundleVersion comes straight from pubspec) — that assertion predated this finding and is now corrected.
  • Fixes CLAUDE.md's release docs, which claimed TestFlight-only iteration works indefinitely on one marketing version. Disproven this session: once a version actually ships, Apple closes its pre-release train entirely (ITMS-90186 "Invalid Pre-Release Train" + ITMS-90062), TestFlight included — a real build attempt against the already-shipped 1.0.0 failed with exactly this.
  • Adds preflight checks in tag-release: version-state gate, tag-already-exists guard (with an accurate message distinguishing "already shipped" from "a previous run's push failed, exists locally only"), and a local-main-in-sync-with-origin check (a stale clone would compute the wrong shipped commit).
  • Factors the semver-format and on-main checks shared by release/tag-release into private recipes (_check-semver-format, _check-on-main), following the existing check: dependency pattern in this justfile.

Known, documented (not fixed) limitation: if a later semver is tag-released before an earlier delayed one, the earlier version's changelog section can repeat commits already published under the later one — flagged with a ponytail: comment at the point it would occur, since a full fix means tracking already-published commit ranges rather than deriving them from tag ancestry, and this ordering is rare for a single-operator release cadence.

Test plan

No app code changed — this is release tooling only (justfile, CLAUDE.md, cliff.toml, ci_post_clone.sh), so the Flutter test suite is unaffected.

  • just --list / just --show release / just --show tag-release parse correctly
  • Exercised _check-semver-format, _check-on-main, _check-tag-absent directly — all fail closed with the expected messages
  • Dry-ran the full tag-release resolution chain against the real, already-shipped 1.0.0 release (no writes): confirmed asc versions view --include-build returns the correct attached build (48, not the newer 49 upload), Xcode Cloud build-run lookup resolves the correct source commit, and git-cliff <range> --output - produces the expected section without touching CHANGELOG.md
  • Did not run just release/just tag-release for real against production Xcode Cloud/App Store Connect

🤖 Generated with Claude Code

`just release <semver>` bumped the version, generated the CHANGELOG entry,
tagged, and started the Xcode Cloud build all in one step — but App Store
review can reject or delay a build for days, during which main keeps moving.
The tag and changelog range ended up pinned to whatever HEAD happened to be
at release time, not to what actually shipped.

`release` now only bumps the version and starts a build. A new `tag-release
<semver>` records the changelog entry and tag, meant to run after App Store
Connect confirms the version is actually live. It resolves the shipped commit
via Xcode Cloud's own build-run records (matching the App Store version's
attached-build number, via `asc versions view --include-build`) rather than
trusting app/pubspec.yaml's build number — build-run history shows Xcode
Cloud assigns CFBundleVersion from its own counter, which doesn't reliably
match what was last bumped in pubspec. Also fixes ci_post_clone.sh's comment,
which asserted the opposite.

Also restores CLAUDE.md's release docs, which claimed TestFlight-only
iteration works indefinitely on one marketing version — disproven this
session: Apple closes a version's pre-release train entirely once it ships
(ITMS-90186 + ITMS-90062), TestFlight included.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@keinstn
keinstn merged commit 62bd87f into main Aug 9, 2026
2 checks passed
@keinstn
keinstn deleted the chore/split-release-recipe branch August 9, 2026 11:46
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