Take the version from the git tag instead of project.pbxproj - #127
Open
andiwand wants to merge 2 commits into
Open
Take the version from the git tag instead of project.pbxproj#127andiwand wants to merge 2 commits into
andiwand wants to merge 2 commits into
Conversation
The marketing version was a number committed in project.pbxproj, which describes either a release that already went out or a guess at the next one, so it was wrong almost all of the time and had to be bumped by hand before every release. The build number stopped being committed when the release moved into CI - it comes from what TestFlight already has - and this does the same for the other half: project.pbxproj holds 0.0.0, the release workflow runs on a version tag, and the tag reaches xcodebuild as MARKETING_VERSION by way of fastlane's ODR_VERSION. Cutting a release is now a tag, and needs no commit before or after it. resolve-version.py decides which run may build which version, and says why: a tag push builds the tag, a dispatched run builds its version input, and neither is only allowed for a dry run. It sits right behind the checkout, because a run that cannot name a version should end before twenty minutes of conan and xcodebuild, not after. Being a script rather than a run: block, it can be run by hand to see what a dispatch would do. The dry run is the second half: it builds, signs and archives the ipa without uploading it, which is the only way to exercise the signing path without putting a build on TestFlight. It is also the only kind of run allowed to go without a version, since uploading 0.0.0 would be rejected by App Store Connect at the very end. Both settings reach fastlane through the environment rather than as lane options, which fastlane passes through as strings - dry_run:false would have arrived as the string "false" and read as true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W5UtM8wFTKh9dGjnx2X2cs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 562c6d09e3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Two things the tag change got wrong, both from copying the android side where they are safe. The concurrency key was narrowed to the ref. Droid can do that because its version code is derived from the tag, so two runs cannot collide on one. Here the build number is a live query of what TestFlight has, and two overlapping runs read the same answer - the second upload then carries a version and build pair App Store Connect has already taken. Back to one release at a time, whatever ref it is on. The lane trusted the workflow to have checked for a version, but these lanes are documented as runnable by hand, where nothing had checked: an unversioned local deploy built 0.0.0 and only found out at the upload, twenty minutes later, that it is below what is live. It now says so before the build, the way resolve-version.py does in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W5UtM8wFTKh9dGjnx2X2cs
andiwand
enabled auto-merge (squash)
July 27, 2026 20:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
The marketing version was a number committed in
project.pbxproj— which describes either a release that already went out or a guess at the next one, so it was wrong almost all of the time and had to be bumped by hand before every release. The build number already stopped being committed when the release moved into CI; this does the same for the other half.Both halves of the version now come from outside the tree:
MARKETING_VERSION(CFBundleShortVersionString)0.0.0CURRENT_PROJECT_VERSION(CFBundleVersion)1Cutting a release is
git tag 1.36 && git push --tags, and needs no commit before or after it. This is the same move OpenDocument.droid made, minus the version-code arithmetic — iOS has no equivalent of Play's monotonic integer, so the tag is just handed through.What changed
release.ymlruns on a version tag (1.36, orv1.36), and keeps theworkflow_dispatchpath with aversioninput for runs off a branch — how a release whose upload failed gets finished from the branch it was cut from..github/scripts/resolve-version.pydecides which run may build which version and says why. It sits right behind the checkout, because a run that cannot name a version should end before twenty minutes of conan and xcodebuild, not after. Being a script rather than arun:block, it can be run by hand to see what a dispatch would do.dry_runinput builds, signs and archives the.ipawithout uploading — the only way to exercise the signing path without putting a build on TestFlight. It is also the only kind of run allowed to go without a version, since uploading0.0.0would be rejected by App Store Connect at the very end.MARKETING_VERSIONrides the same pathCURRENT_PROJECT_VERSIONalready did: an xcarg on the command line, so a release never leaves the working tree dirty. Both reach fastlane through the environment rather than as lane options, which fastlane passes through as strings —dry_run:falsewould have arrived as the string"false"and read as true.Note before merging
1.35 is live, so the first tag has to be 1.36 or higher — App Store Connect rejects anything not above what is published.
Verified
xcodebuild -showBuildSettingswith the override resolvesMARKETING_VERSION = 1.36, and without it0.0.0/1— so the command-line setting reaches the bundle, which takes both from$(...)inInfo.plist.resolve-version.pyexercised across tag push, v-prefixed tag, dispatch with and without a version, an input disagreeing with the tag, a malformed version, and a shell-injection-shaped one.Not verified: a full release run, which needs the secrets and a real upload. The first tag push is the real test —
dry_runis there to take it first if you'd rather.