From a25acf6e351029b22ec005255b1df427ff3f5641 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 18 Aug 2026 14:46:34 +0200 Subject: [PATCH] Retry a failed upload instead of losing the release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publishing 1.2.0 put the first package up and then failed the second with "invalid username, password or token" — the same token, seconds later, and the identical command succeeded on the next run. A flake at 118 MB should not cost a rebuild, so each upload gets up to three attempts, and the attachment already on the release is dropped before every attempt so a retry cannot leave two copies. The shipped bundle was checked rather than assumed: unpacked from the release zip, `codesign --verify --deep --strict` is valid on disk and satisfies its designated requirement, and the app drives the real store — ten cards, the menu populated, the categories counted. With the quarantine flag set it is killed on launch (exit 137), which is the ad-hoc-signing limitation the README already documents. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 10 ++++++++++ scripts/release.sh | 41 +++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4feab58..9d0f6ef 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,16 @@ Release notes come from `RELEASE_NOTES.md` when the file is present, otherwise t release gets a one-line note. The repository is read from `origin`, so a fork publishes to the fork. +Each upload is retried up to three times, and the existing attachment is dropped +before every attempt so a retry cannot leave two copies. A 100 MB upload does fail +on its own: publishing 1.2.0 got *"invalid username, password or token"* on the +second package while the first had just gone up with the same token, and the same +command succeeded immediately afterwards. + +Package names contain a space — `WarpEngine Store-1.2.0-arm64.dmg` — so the list of +files is passed one path per line rather than as one string; splitting it on +whitespace is what broke the first attempt at publishing 1.1.0. + It needs `tea` installed and logged in — the devarea repo has `make tea` for that. Overridable: `TAG`, `REPO`, `TEA_LOGIN`, `NOTES`, `DIST`. diff --git a/scripts/release.sh b/scripts/release.sh index 4636635..69e3e18 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -99,27 +99,44 @@ RELEASE_ID="$(release_id)" [ -n "$RELEASE_ID" ] || die "the release $TAG could not be created or found" # --- the attachments ------------------------------------------------------- -while IFS= read -r asset; do - [ -n "$asset" ] || continue - [ -f "$asset" ] || die "no such file: $asset" - name="$(basename "$asset")" - - # Replacing rather than refusing: a second run after a rebuild should land. - old="$(tea api "/repos/$REPO/releases/$RELEASE_ID/assets" | python3 -c " +# Anything already attached under this name, dropped: replacing rather than +# refusing is what makes a rebuild-and-upload repeatable. Called before every +# attempt, so a retry cannot leave two copies behind. +drop_existing() { + ids="$(tea api "/repos/$REPO/releases/$RELEASE_ID/assets" | python3 -c " import json, sys name = sys.argv[1] for a in json.load(sys.stdin): if a['name'] == name: print(a['id']) -" "$name")" - for id in $old; do - say "replacing $name" +" "$1")" + for id in $ids; do + say "replacing $1" tea api -X DELETE "/repos/$REPO/releases/$RELEASE_ID/assets/$id" >/dev/null done +} +while IFS= read -r asset; do + [ -n "$asset" ] || continue + [ -f "$asset" ] || die "no such file: $asset" + name="$(basename "$asset")" size="$(python3 -c "import os,sys; print(f'{os.path.getsize(sys.argv[1])/1e6:.0f} MB')" "$asset")" - say "uploading $name ($size) — large packages take a few minutes" - tea releases assets create --login "$LOGIN" --repo "$REPO" "$TAG" "$asset" >/dev/null + + # Retried, because a 100 MB upload does fail on its own: publishing 1.2.0 got + # "invalid username, password or token" on the second package while the first + # had just gone up with the same token, and the identical command succeeded on + # the next run. One flake should not cost a rebuild. + attempt=1 + while :; do + drop_existing "$name" + say "uploading $name ($size) — large packages take a few minutes" + if tea releases assets create --login "$LOGIN" --repo "$REPO" "$TAG" "$asset" >/dev/null; then + break + fi + [ "$attempt" -lt 3 ] || die "$name could not be uploaded after $attempt attempts" + attempt=$((attempt + 1)) + say "that failed — attempt $attempt of 3" + done done < "$LIST" say "done:"