diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de9823b..86853de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,15 +2,25 @@ # # Manual-only release: cuts app/build.gradle.kts's versionName/versionCode and # CHANGELOG.md's [Unreleased] section for the version given in the dispatch -# form, commits and pushes that bump straight to main, then builds a signed -# release APK and publishes it as a GitHub Release. The version input is -# always typed by hand (no auto-computed default — GitHub's dispatch form -# can't pre-fill a value computed from repo state) and is only validated to -# be greater than the current versionName. The workflow still refuses to -# overwrite an existing release/tag. The GitHub Release body is never the -# full cut section — see "Extract changelog highlights" below for what -# actually gets published, and CLAUDE.md's "Changelog and release process" -# for the entry convention it depends on. +# form (as local, uncommitted edits), builds and signs the release APK +# against those edits, and only once the GitHub Release is actually +# published does it commit and push the version bump straight to main. This +# order is deliberate: if the build/signing step fails (e.g. a bad keystore +# password), main is never touched and the same version input can simply be +# re-run once the underlying problem is fixed — nothing is "spent" on a +# failed attempt. An earlier version of this workflow committed the bump +# before building, which meant a signing failure would leave main +# permanently bumped/changelog-cut with no release ever published for that +# version, and no clean way to retry short of faking a new changelog entry +# — see docs/implementation-decisions.md (found and fixed via the sibling +# HackDex-Tracker project, which hit this for real on its first release). +# The version input is always typed by hand (no auto-computed default — +# GitHub's dispatch form can't pre-fill a value computed from repo state) +# and is only validated to be greater than the current versionName. The +# workflow still refuses to overwrite an existing release/tag. The GitHub +# Release body is never the full cut section — see "Extract changelog +# highlights" below for what actually gets published, and CLAUDE.md's +# "Changelog and release process" for the entry convention it depends on. name: Release on: @@ -103,18 +113,9 @@ jobs: sed -i -E "s/versionCode = [0-9]+/versionCode = $NEW_CODE/" app/build.gradle.kts sed -i -E "s/versionName = \"[^\"]*\"/versionName = \"$NEW_VERSION\"/" app/build.gradle.kts - - name: Commit and push version bump - env: - NEW_VERSION: ${{ inputs.version }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add CHANGELOG.md app/build.gradle.kts - git commit -m "Cut release $NEW_VERSION" - git push origin HEAD:main - # Single source of truth for the release version: app/build.gradle.kts's versionName, - # now freshly bumped by the step above. + # now freshly bumped by the step above (still only a local, uncommitted edit at this + # point — see the top-of-file comment for why the commit/push is deferred to the end). - name: Read app version id: version run: | @@ -270,3 +271,19 @@ jobs: --repo "$GITHUB_REPOSITORY" \ --title "ThePatientGamerHelper $RELEASE_TAG" \ --notes-file release-notes.md + + # Only reached once the release above actually exists. Pushes with the credentials the + # initial Checkout step already configured for this job (its RELEASE_PUSH_TOKEN input) — + # a PAT, not the default GITHUB_TOKEN, so this commit authenticates as an actual repo admin + # and can bypass main's branch protection (a GITHUB_TOKEN push is attributed to the + # github-actions[bot] identity, which isn't covered by an "admins bypass required pull + # requests" exception). + - name: Commit and push version bump + env: + NEW_VERSION: ${{ inputs.version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add CHANGELOG.md app/build.gradle.kts + git commit -m "Cut release $NEW_VERSION" + git push origin HEAD:main diff --git a/CHANGELOG.md b/CHANGELOG.md index 32bda44..12cf570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ versioning follows the app's `versionName` in `app/build.gradle.kts`. ## [Unreleased] +- **Fixed a `release.yml` bug that could burn a version number on a failed signed build.** The + workflow used to commit and push the `versionName`/`versionCode` bump and the cut + `CHANGELOG.md` section to `main` *before* attempting the signed build. Found on the sibling + HackDex-Tracker project, which hit this for real on its first release run: a signing failure + left `main` permanently bumped with no release ever published, and every retry then failed + immediately because `[Unreleased]` was already empty. This project's releases have all + succeeded so far, but the same latent bug applied here. Reordered so the commit/push only + happens after `gh release create` actually succeeds; a build/signing failure now leaves `main` + untouched and the same version can simply be re-run. See `docs/implementation-decisions.md`. + ## [1.0.5] - 2026-08-20 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 6f733aa..adfa767 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -788,8 +788,13 @@ something to do unprompted) is a single step: manually trigger the and leaves a fresh empty `## [Unreleased]` above it. 3. Bumps `versionCode` (+1) and `versionName` in `app/build.gradle.kts` to match. -4. Commits and pushes that bump straight to `main`, then proceeds to - build/sign/publish the release from those same files. +4. Builds/signs/publishes the release from those same files, still only + as local, uncommitted edits at this point, and **only after the + GitHub Release is actually published** commits and pushes that bump + straight to `main`. This order is deliberate — see + `docs/implementation-decisions.md`: a build/signing failure (e.g. a + bad keystore password) now leaves `main` untouched instead of burning + the version number with no release to show for it. There is no auto-computed default for the `version` input — GitHub's manual dispatch form can't pre-fill a value computed from repo state, so diff --git a/docs/implementation-decisions.md b/docs/implementation-decisions.md index 0c70e7b..08c9095 100644 --- a/docs/implementation-decisions.md +++ b/docs/implementation-decisions.md @@ -1211,3 +1211,29 @@ scope this task didn't ask for — a new persistence surface for the former, a `backlog_items` Room migration for the latter — for a benefit (repeated cold-start lookups, one more stat) nobody had flagged as needed. Add either separately if a concrete need shows up. + +## `release.yml` now commits the version bump *after* the signed build, not before + +Found on the sibling `HackDex-Tracker` project, which hit this for real +on its first `Release` run: the workflow used to commit and push the +`versionName`/`versionCode` bump and the cut `CHANGELOG.md` section to +`main` *before* attempting the signed build. If that build then fails +(a keystore-password mismatch, an expired secret, anything in the +signing/build step), `main` is left permanently bumped with +`[Unreleased]` emptied out, but no GitHub Release ever published for +that version — every retry with a higher version number then fails +immediately with "`CHANGELOG.md`'s `[Unreleased]` section is empty - +nothing to release", since there's no new changelog content to cut. The +version number is effectively burned with nothing to show for it, and +the only way out would be fabricating a new changelog entry just to +unblock the workflow. + +This project's own releases have all succeeded so far (this was a +latent bug here, not one that had actually bitten a real run), but the +same reordering applies: the version-bump/changelog-cut edits are still +made first, but only as local, uncommitted working-tree changes. The +signed build runs against those local edits, and the `git commit`/ +`git push` to `main` is now the very last step, reached only once +`gh release create` has actually succeeded. A build/signing failure now +leaves `main` completely untouched, so the exact same version input can +simply be re-run once the underlying problem is fixed.