From 36beb2d0ad3c3a043207dcf6135cc632ed6f39e0 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 20 Jul 2026 19:50:57 +0200 Subject: [PATCH] ci(release): wait for the mirrored tag, never mint it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Codeberg publish has never once succeeded — 0.2.1, 0.2.2, 0.3.0, 0.3.1 and 0.3.2 all failed, and 0.3.0 was published by hand. Neither previous fix could have worked, because the cause isn't in this file: ref writes to jlmakiola/agendula on Codeberg fail. A tag push returns "cannot lock references" and POST /tags returns an empty-bodied 500, while the identical calls succeed on jlmakiola/calendula with the same token. Creating a release mints a tag, so it inherited the same failure. Attaching a release to a tag that is ALREADY there needs no ref write and returns 201. So split the responsibility the way it should have been: the push mirror owns delivering the tag, this step only attaches to it. Poll for the tag, then POST with no target_commitish so the API attaches rather than resolves a commit and mints one. If the tag never lands, fail with a pointer at the mirror instead of trying to create it. Drop continue-on-error. It reported green across five releases that never published, which is how 0.3.1's crash fix reached F-Droid while the Codeberg and Obtainium users who hit the crash got nothing. Tested against the live API before committing (unlike its predecessors): tag-present attaches 201, tag-absent exits 1 with the diagnostic. Note this does not by itself restore publishing — the mirror cannot write the v0.3.2 tag either, so Codeberg must repair the repo's ref store first. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/release.yaml | 60 +++++++++++++++++++++-------------- cb-payload.json | 1 + cb-response.json | 1 + release-notes.md | 5 +++ 4 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 cb-payload.json create mode 100644 cb-response.json create mode 100644 release-notes.md diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 2332f10..48ef4ec 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -370,11 +370,13 @@ jobs: # release. Needs the CODEBERG_RELEASE_TOKEN secret; skips cleanly if unset. - name: Publish release to Codeberg if: env.IS_RELEASE == 'true' - continue-on-error: true + # NOT continue-on-error: this step reported green through 0.2.1, 0.2.2, + # 0.3.0, 0.3.1 and 0.3.2 while never once publishing, which is how a + # crash-fix release reached F-Droid but not the Codeberg/Obtainium + # users who needed it. A broken mirror must fail the release loudly. env: TOKEN: ${{ secrets.CODEBERG_RELEASE_TOKEN }} API: https://codeberg.org/api/v1/repos/jlmakiola/agendula - SHA: ${{ github.sha }} run: | set -e if [ -z "${TOKEN:-}" ]; then @@ -399,29 +401,39 @@ jobs: sed -i -e '/./,$!d' release-notes.md fi [ -s release-notes.md ] || echo "_See CHANGELOG.md for ${VERSION}._" > release-notes.md - # Forgejo 500s on POST /releases when the tag ALREADY exists, and a - # bare tag is not a release — so GET /releases/tags then 404s and the - # upsert has no id to fall back on ("Could not resolve Codeberg - # release id", the 0.3.1 failure). Pushing the tag first therefore - # guarantees the 500 rather than avoiding it; 0.3.0 published because - # it POSTed while the tag was still absent. So: POST with the tag - # ABSENT and let the API mint tag + release together from - # target_commitish. Branches mirror reliably, so the commit is already - # on Codeberg — only a tag the mirror raced in ahead of us is in the - # way, and clearing it is safe precisely because no release owns it. - ID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty') - if [ -z "$ID" ]; then - curl -s -o /dev/null -w "codeberg tag DELETE HTTP %{http_code}\n" -X DELETE \ - -H "Authorization: token $TOKEN" "$API/tags/$TAG" + # Never mint the tag here. Gitea's push mirror owns getting it to + # Codeberg; this step's only job is to attach a release to a tag that + # has already landed. That split matters because every way of creating + # a tag from here — git push, or a release POST carrying + # target_commitish for a tag Codeberg lacks — is a ref WRITE, and ref + # writes are what fail on this repo ("cannot lock references" on push, + # an empty-bodied 500 on the API). Attaching to a tag that is already + # present needs no ref write and succeeds. + # + # So: wait for the mirror, verify, then attach. If the tag never shows + # up, fail — do NOT fall back to creating it, which is what produced + # the silent breakage across 0.2.1 through 0.3.2. + TAG_OK="" + for i in $(seq 1 30); do + if [ "$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: token $TOKEN" "$API/tags/$TAG")" = "200" ]; then + TAG_OK=1; echo "Codeberg has $TAG (after ~$((i*10))s)"; break + fi + sleep 10 + done + if [ -z "$TAG_OK" ]; then + echo "Codeberg never received $TAG from the push mirror (waited 300s)." >&2 + echo "Not creating it here: ref writes to this repo fail, so that" >&2 + echo "would 500. Check the mirror, then re-run once the tag is there." >&2 + exit 1 fi - python3 - "$TAG" "$SHA" "$PRERELEASE" <<'PY' > cb-payload.json + # No target_commitish: the tag exists, so the API must attach to it + # rather than resolve a commit and mint one. + python3 - "$TAG" "$PRERELEASE" <<'PY' > cb-payload.json import json, sys - tag, sha, pre = sys.argv[1:4] + tag, pre = sys.argv[1:3] print(json.dumps({ "tag_name": tag, - # Recreate the tag as part of the release; the commit is on - # Codeberg already via the branch mirror. - "target_commitish": sha, "name": tag, "body": open("release-notes.md").read(), "draft": False, @@ -429,9 +441,9 @@ jobs: "prerelease": pre == "true", })) PY - # Upsert (re-run safe): a release that already exists is PATCHed in - # place — the delete above is skipped in that case, so re-running - # never disturbs a published release. + # Upsert (re-run safe): a release already attached to this tag is + # PATCHed in place, so re-running never disturbs a published release. + ID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty') if [ -n "$ID" ]; then curl -s -o /dev/null -w "release PATCH HTTP %{http_code}\n" -X PATCH \ -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ diff --git a/cb-payload.json b/cb-payload.json new file mode 100644 index 0000000..a12b86a --- /dev/null +++ b/cb-payload.json @@ -0,0 +1 @@ +{"tag_name": "v0.3.1", "name": "v0.3.1", "body": "### Fixed\n- Agendula no longer crashes on launch. Every 0.3.0 install was affected: the\n release build stripped a constructor that the background-work scheduler needs\n to open its database, and that happens before the app draws anything.\n\n", "draft": true, "prerelease": true} diff --git a/cb-response.json b/cb-response.json new file mode 100644 index 0000000..1624aca --- /dev/null +++ b/cb-response.json @@ -0,0 +1 @@ +{"id":10985210,"tag_name":"v0.3.1","target_commitish":"","name":"v0.3.1","body":"### Fixed\n- Agendula no longer crashes on launch. Every 0.3.0 install was affected: the\n release build stripped a constructor that the background-work scheduler needs\n to open its database, and that happens before the app draws anything.\n\n","url":"https://codeberg.org/api/v1/repos/jlmakiola/agendula/releases/10985210","html_url":"https://codeberg.org/jlmakiola/agendula/releases/tag/v0.3.1","tarball_url":"https://codeberg.org/jlmakiola/agendula/archive/v0.3.1.tar.gz","zipball_url":"https://codeberg.org/jlmakiola/agendula/archive/v0.3.1.zip","hide_archive_links":false,"upload_url":"https://codeberg.org/api/v1/repos/jlmakiola/agendula/releases/10985210/assets","draft":true,"prerelease":true,"created_at":"2026-07-20T19:50:15+02:00","published_at":"2026-07-20T19:50:15+02:00","author":{"id":1256480,"login":"jlmakiola","login_name":"","source_id":0,"full_name":"Jean-Luc Makiola","email":"jlmakiola@noreply.codeberg.org","avatar_url":"https://codeberg.org/avatars/0b2d6a937427776f023efc504039607c8b24c7ad0331e6d7171d86cca11c8e78","html_url":"https://codeberg.org/jlmakiola","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-06-22T10:25:16+02:00","restricted":false,"active":false,"prohibit_login":false,"location":"Germany","pronouns":"he/him","website":"https://jeanlucmakiola.de","description":"I build privacy-respecting open-source apps — modern, well-designed, and made to last.","visibility":"public","followers_count":2,"following_count":0,"starred_repos_count":0,"username":"jlmakiola"},"assets":[],"archive_download_count":{"zip":0,"tar_gz":0}} diff --git a/release-notes.md b/release-notes.md new file mode 100644 index 0000000..9bfebc3 --- /dev/null +++ b/release-notes.md @@ -0,0 +1,5 @@ +### Fixed +- Agendula no longer crashes on launch. Every 0.3.0 install was affected: the + release build stripped a constructor that the background-work scheduler needs + to open its database, and that happens before the app draws anything. +