Secret Scan / scan (push) Successful in 3s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-linux (pull_request) Failing after 2m3s
Build App (Preview) / build-macos (pull_request) Successful in 2m41s
Build App (Preview) / build-windows (pull_request) Successful in 4m56s
Build App (Preview) / prune-previews (pull_request) Skipped
Second review of this branch. Two blockers and one real defect I had papered over with a true-but-misleading claim. **`make_latest` was missing from the republish path.** The create path sends `"make_latest": "false"` so the channel cannot displace the versioned release on the releases page. The reuse path — taken on every run after the first — omitted it, and the API's documented default for a publish transition is `true`. So the second release would have quietly promoted `linux-latest` to the repository's Latest release: a release whose own body says "for a specific version, use the versioned releases instead". Now sent on both paths. `tag_name` is re-sent deliberately and now says so in a comment — the API removes the tag when a PATCH omits it, and this branch exists because a tag disappeared. **A transient Gitea error would have cost the whole release.** `curl -sf` fails identically for "404, the tag is genuinely absent" and "503, Gitea is briefly unreachable", and both landed in the create branch. Creating a tag that already exists returns 409, which aborted the last step of `build-linux` — and `create-tag` and `sync-to-github` both depend on it, so no version tag and no GitHub sync at all. The failure message also read "the tag does not exist" when Gitea had merely been unreachable. Now a `case` on the HTTP code — 200 leave alone, 404 create, anything else fail loudly with the real code — the same idiom `Upload to Gitea release` already uses two steps above. `422 already_exists` on the release POST is likewise a recoverable answer, not a reason to lose a release. **The empty `Categories=` was still shipping, and my claim hid it.** I wrote that the guard "asserts the absence of an empty value rather than the presence of any filled one" — true of the regex, false of the artifact. The AppDir root `.desktop` is a *symlink* into usr/share/applications, so `sed -i` replaced the link with a regular file and left the real entry empty; the guard globbed the root only, so it saw the copy it had just written and passed. Verified on the real artifact: two divergent entries, and the one that shipped was empty. Fixed with `--follow-symlinks`, both locations globbed, and the guard turned into a positive assertion over every entry — which also closes its missing-key and unmatched-glob holes. Both entries now read `Categories=Development;Utility;`. Also taken: the duplicate-AppImage check moves to a precondition, since as a post-mortem it let the script repack and overwrite the versioned artifact before failing, and it silently selected by glob order, i.e. the older version — it now refuses in under a second; assets are deleted and re-uploaded one at a time, because deleting both up front left a fresh AppImage with no .zsync if the second upload failed, which silently stops every client; and the success line no longer claims a fallback was kept when there was nothing to demote. Left as informational, with the reasoning recorded rather than acted on: `--retry-all-errors` retries permanent 4xx (fail-closed, matches the repo's other upload steps); the release list is unpaginated (a GraphQL lookup by pending tag name is the durable fix, but 7 releases is decades from the cliff, and the 422 handling above covers the failure mode); process-substitution failure is invisible to `mapfile` (fail-closed downstream). Verified against the real 0.4.19 artifact — happy path, no AppImage, two AppImages, and an AppDir rebuilt with the bundled library removed. shellcheck clean at warning level on both scripts. appimagetool now reports the AppStream metadata found. Nothing here is CI-proven, and that is worth stating plainly: `build-linux` fails on this branch before `tauri build` even runs, at "Install frontend dependencies" with `npm error Cannot read properties of null (reading 'edgesOut')` — confirmed in the logs of jobs 5644 and 5636. Unrelated to this change and tracked separately, but it means the finalizer has never executed in CI on either commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YPqHpjV4EL6RNEwrRKqQm