From ecbf1b16414e3eecb49ca387689d79f71fe4ffbd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 21:48:22 +0000 Subject: [PATCH] Fix release.yml: commit version bump only after a successful signed build Same latent bug found on the sibling HackDex-Tracker project, which hit it for real on its first release run: the workflow committed and pushed the versionName/versionCode bump and cut CHANGELOG.md straight to main *before* attempting the signed build. If that build fails (bad keystore password, expired secret, etc.), main is left permanently bumped with no GitHub Release ever published for that version, and every retry then fails immediately because [Unreleased] is already empty. This project's own releases have all succeeded so far, but the same reordering closes the latent gap: the version-bump/changelog-cut edits stay local/uncommitted through the build, and the commit+push only happens after gh release create actually succeeds. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01XHyrsY7NaqPBFy7T7fxCc5 --- .github/workflows/release.yml | 57 +++++++++++++++++++++----------- CHANGELOG.md | 10 ++++++ CLAUDE.md | 9 +++-- docs/implementation-decisions.md | 26 +++++++++++++++ 4 files changed, 80 insertions(+), 22 deletions(-) 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.