From f37a02eb7bac5fb1c5d09fc2922dc990864a5523 Mon Sep 17 00:00:00 2001 From: Bryan Roscoe Date: Wed, 16 Sep 2026 21:54:11 -0500 Subject: [PATCH] Release tooling: fix the push race, add --yes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two of the three changes from #84, rebased onto current main. That PR is 89 commits behind and its third change — renaming the tag prefix from `v2-` to `desktop-` — is deliberately left out; see the PR description. **Push race.** `merge-updater` and `refresh-screenshots` both commit to the default branch and can finish at the same time, and neither rebased before pushing, so whichever lost got a non-fast-forward rejection. That is what failed the v2-2.1.0 gallery job. Both now rebase and retry, up to five times, then fail loudly. Verified the loop under `set -euo pipefail`: it succeeds on recovery, gives up after five, and the `[[ ]] && { exit 1; }` guard does not trip `set -e` on the non-final attempts. **`--yes` / `-y`.** Auto-confirms the three gates for non-interactive use. Piping `yes` into the script stays blocked, which is correct — this is the sanctioned way to skip the gates on purpose. The prompts collapse into one `confirm` helper; verified it proceeds under `--yes` and on a typed `y`, and aborts on `n` and on empty stdin, so it still fails closed. --- .github/workflows/v2-release.yml | 24 ++++++++++++++++++++++-- CLAUDE.md | 2 +- v2/release.sh | 31 ++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/.github/workflows/v2-release.yml b/.github/workflows/v2-release.yml index cac29b6..878c382 100644 --- a/.github/workflows/v2-release.yml +++ b/.github/workflows/v2-release.yml @@ -385,7 +385,17 @@ jobs: echo "Updater manifest unchanged; nothing to commit." else git commit -m "Update updater manifest for ${TAG}" - git push origin HEAD:${{ github.event.repository.default_branch }} + # `merge-updater` and `refresh-screenshots` both commit to the + # default branch and can finish at the same time, so the loser of + # the race gets a non-fast-forward rejection. That is what failed + # the v2-2.1.0 gallery job. Rebase and retry instead. + branch="${{ github.event.repository.default_branch }}" + for attempt in 1 2 3 4 5; do + if git push origin "HEAD:$branch"; then break; fi + echo " push attempt $attempt rejected; rebasing on origin/$branch…" + git pull --rebase origin "$branch" + [[ $attempt -eq 5 ]] && { echo "push still failing after rebase"; exit 1; } + done echo "Updater manifest published for ${TAG}." fi @@ -448,5 +458,15 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add screenshots/gallery.gif screenshots/gallery-light.gif git commit -m "Regenerate gallery GIFs for ${TAG}" - git push origin HEAD:${{ github.event.repository.default_branch }} + # `merge-updater` and `refresh-screenshots` both commit to the + # default branch and can finish at the same time, so the loser of + # the race gets a non-fast-forward rejection. That is what failed + # the v2-2.1.0 gallery job. Rebase and retry instead. + branch="${{ github.event.repository.default_branch }}" + for attempt in 1 2 3 4 5; do + if git push origin "HEAD:$branch"; then break; fi + echo " push attempt $attempt rejected; rebasing on origin/$branch…" + git pull --rebase origin "$branch" + [[ $attempt -eq 5 ]] && { echo "push still failing after rebase"; exit 1; } + done echo "Galleries refreshed for ${TAG}." diff --git a/CLAUDE.md b/CLAUDE.md index 58be9f4..e63c7ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,7 +114,7 @@ The release workflow also regenerates the gallery on every `v2-*` tag (the `refr - WiX rejects non-numeric pre-release identifiers — `0.1.0-beta` fails with "optional pre-release identifier in app version must be numeric-only…". So `release.sh` sets a numeric-only `bundle.windows.wix.version` (the human version stays as-is for Linux/macOS/NSIS). - **Windows Installer ignores the 4th version field** for upgrade detection — it compares only `major.minor.build`. An earlier scheme put the pre-release counter in the 4th field (`0.1.0.N`), so every beta read as `0.1.0` and Windows refused in-place upgrades ("uninstall the existing version first"). `release.sh` now encodes the counter into the **3rd (build)** field so each release strictly increases in semver order: `build3 = patch*1000 + typeBase + n` with type bands alpha 0 / beta 300 / rc 600 / stable 999 (e.g. `0.1.0-beta.3` → `0.1.303`, `0.1.0-rc.1` → `0.1.601`, `0.1.0` → `0.1.999`). If you hand-bump, set `bundle.windows.wix.version` with the same scheme. - The MSI **UpgradeCode** is auto-derived by Tauri from `productName`/`identifier` and must stay stable for upgrades to work — **don't rename the product or change the identifier** without understanding it resets the UpgradeCode and orphans existing installs. -- **Don't pipe `yes` into `release.sh`.** The auto-mode classifier blocks it (correctly) — the script's interactive gates are the safety net. Run it interactively, or do the steps by hand. +- **Don't pipe `yes` into `release.sh`.** The auto-mode classifier blocks it (correctly) — the script's interactive gates are the safety net. Run it interactively, or pass `--yes` if you have deliberately decided to skip the gates (CI, or an agent acting on an explicit instruction). ### Homebrew tap diff --git a/v2/release.sh b/v2/release.sh index 2df63fb..1a34779 100755 --- a/v2/release.sh +++ b/v2/release.sh @@ -34,12 +34,31 @@ BUMP="patch" PRE="" EXPLICIT="" +# Piping `yes` into this script is blocked by the auto-mode classifier, and +# rightly so — these gates are the safety net. `--yes` is the sanctioned way to +# skip them deliberately, for CI or an agent that has already decided. +ASSUME_YES=0 + +# Ask a y/N question, or answer it automatically under --yes. Returns 0 for +# yes, 1 for no, so callers read as `confirm "..." || abort`. +confirm() { + local prompt="$1" + if [[ "$ASSUME_YES" == "1" ]]; then + echo "$prompt [auto-yes]" + return 0 + fi + local REPLY + read -p "$prompt " -n 1 -r; echo + [[ $REPLY =~ ^[Yy]$ ]] +} + usage() { cat < 0.1.1 @@ -61,6 +80,7 @@ while [[ $# -gt 0 ]]; do --alpha) PRE="alpha"; shift ;; --preview) PRE="preview"; shift ;; --set) EXPLICIT="$2"; shift 2 ;; + --yes|-y) ASSUME_YES=1; shift ;; -h|--help) usage ;; *) echo "Unknown flag: $1" >&2; usage ;; esac @@ -76,8 +96,7 @@ fi if [[ -n "$(git status --porcelain)" ]]; then echo "Working tree has uncommitted changes:" >&2 git status --short >&2 - read -p "Continue anyway? (y/N) " -n 1 -r; echo - [[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } + confirm "Continue anyway? (y/N)" || { echo "Aborted."; exit 1; } fi # --- Read current version ---------------------------------------------------- @@ -124,8 +143,7 @@ if git rev-parse "$TAG" >/dev/null 2>&1; then exit 1 fi -read -p "Bump + tag? (y/N) " -n 1 -r; echo -[[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } +confirm "Bump + tag? (y/N)" || { echo "Aborted."; exit 1; } # --- Patch the version into all three files --------------------------------- @@ -212,8 +230,7 @@ git add "$TAURI_CONF" "$CARGO_TOML" "$CORE_CARGO_TOML" "$CARGO_LOCK" "$PACKAGE_J git commit -m "Release $TAG" git tag -a "$TAG" -m "Release $TAG" -read -p "Push the tag? (this fires the build workflow) (y/N) " -n 1 -r; echo -if [[ $REPLY =~ ^[Yy]$ ]]; then +if confirm "Push the tag? (this fires the build workflow) (y/N)"; then git push origin HEAD git push origin "$TAG" echo