Update check: read releases.rss instead of tags.rss so a version is never offered before its zip exists #26

Open
opened 2026-10-16 16:43:07 -07:00 by eric · 1 comment
Owner

Problem

UpdateChecker reads tags.rss. A tag shows up in that feed as soon as it's pushed, which is a few minutes before release.yml attaches the zip. If a site checks for updates in that window, it's offered an update whose package URL returns 404. The README and CHANGELOG (1.0.3) document this as a known limitation.

Proposal

Read releases.rss instead, as apprise-wordpress-notifications 2.6.0 now does:

  • Draft releases don't appear in releases.rss. When the workflow creates the release, it starts as a draft, gets the zip attached, and is then published ("draft": false). So for the normal flow (push a tag), a version is listed only after its zip can be downloaded.
  • Parse the version from each item's <link> (…/releases/tag/<version>), not from <title>. In this feed the title is the release name, which can be anything if the release was named by hand in the UI.
  • Keep the current rules: strict VERSION_REGEX, highest version wins, the cache re-validation from #23, and the TTLs.

The feed is live and has the expected structure: https://git.pickysysadmin.ca/eric/multi-domain-redux/releases.rss lists 1.0.3 / 1.0.2 / 1.0.1 with <link>…/releases/tag/X.Y.Z</link>.

Reference implementation: Apprise_Update_Checker::parse_feed() in apprise-wordpress-notifications (commit caa0483):

preg_match_all( '#<item\b[^>]*>.*?<link>\s*[^<]*/releases/tag/([^<\s/]+)\s*</link>#s', $xml, $matches )
// then rawurldecode() each match, filter by VERSION_REGEX, take the highest with version_compare().

Remaining gap

A release created in the Forgejo UI is published immediately, before the workflow runs. It shows up in releases.rss before the zip is attached, so the same window still exists on that path. Either document "push the tag; don't create the release in the UI" as the release procedure, or accept the gap for that path only.

To change

  • src/Update/UpdateChecker.php: FEED → /releases.rss; parseFeed() reads <link> instead of <title>
  • tests/Unit/UpdateCheckerTest.php: feed fixtures use release-feed items (links, including a title that differs from the tag)
  • README "Update checks" + CHANGELOG: remove the tag-before-zip caveat (or narrow it to UI-created releases)
  • CLAUDE.md "Releasing": note that releasing by tag push is the path that never offers a version without its zip
## Problem `UpdateChecker` reads `tags.rss`. A tag shows up in that feed as soon as it's pushed, which is a few minutes before `release.yml` attaches the zip. If a site checks for updates in that window, it's offered an update whose package URL returns 404. The README and CHANGELOG (1.0.3) document this as a known limitation. ## Proposal Read `releases.rss` instead, as apprise-wordpress-notifications 2.6.0 now does: - **Draft releases don't appear in `releases.rss`.** When the workflow creates the release, it starts as a draft, gets the zip attached, and is then published (`"draft": false`). So for the normal flow (push a tag), a version is listed only after its zip can be downloaded. - **Parse the version from each item's `<link>`** (`…/releases/tag/<version>`), not from `<title>`. In this feed the title is the release *name*, which can be anything if the release was named by hand in the UI. - Keep the current rules: strict `VERSION_REGEX`, highest version wins, the cache re-validation from #23, and the TTLs. The feed is live and has the expected structure: `https://git.pickysysadmin.ca/eric/multi-domain-redux/releases.rss` lists 1.0.3 / 1.0.2 / 1.0.1 with `<link>…/releases/tag/X.Y.Z</link>`. Reference implementation: `Apprise_Update_Checker::parse_feed()` in apprise-wordpress-notifications (commit `caa0483`): ```php preg_match_all( '#<item\b[^>]*>.*?<link>\s*[^<]*/releases/tag/([^<\s/]+)\s*</link>#s', $xml, $matches ) // then rawurldecode() each match, filter by VERSION_REGEX, take the highest with version_compare(). ``` ## Remaining gap A release **created in the Forgejo UI** is published immediately, before the workflow runs. It shows up in `releases.rss` before the zip is attached, so the same window still exists on that path. Either document "push the tag; don't create the release in the UI" as the release procedure, or accept the gap for that path only. ## To change - `src/Update/UpdateChecker.php`: `FEED` → `/releases.rss`; `parseFeed()` reads `<link>` instead of `<title>` - `tests/Unit/UpdateCheckerTest.php`: feed fixtures use release-feed items (links, including a title that differs from the tag) - README "Update checks" + CHANGELOG: remove the tag-before-zip caveat (or narrow it to UI-created releases) - CLAUDE.md "Releasing": note that releasing by tag push is the path that never offers a version without its zip
Author
Owner

Confirmed with the maintainer: releases are always cut by pushing a bare version tag, never by creating the release in the Forgejo UI. So the UI-created gap is moot in practice. Document tag-push as the release procedure in CLAUDE.md, and drop the tag-before-zip caveat from the README/CHANGELOG instead of narrowing it.

Confirmed with the maintainer: releases are always cut by pushing a bare version tag, never by creating the release in the Forgejo UI. So the UI-created gap is moot in practice. Document tag-push as *the* release procedure in CLAUDE.md, and drop the tag-before-zip caveat from the README/CHANGELOG instead of narrowing it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eric/multi-domain-redux#26
No description provided.