Skip to content

Offer an update only for the release GitHub calls latest - #667

Merged
thcp merged 1 commit into
mainfrom
fix/update-only-latest-666
Sep 22, 2026
Merged

thcp merged 1 commit into
mainfrom
fix/update-only-latest-666

Conversation

@thcp

@thcp thcp commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #666

GitHub's publish dialog has three states, not two: set as the latest release, set as a pre-release, or neither. The updater could only tell two of them apart, and treated the third as latest.

What was wrong

checkForUpdate read the list endpoint and took the first release that was neither a draft nor a pre-release:

releases.find((r) => !r.draft && !r.prerelease)

A release published with nothing ticked comes back as draft: false, prerelease: false, indistinguishable from a promoted one by any field the list carries, so it was pushed to every install.

This is not a fault anyone hits by following the release runbook, which publishes as a pre-release and promotes afterwards. It is a fault that turns one slip in the publish dialog into a build shipped to everybody. It is also worth noting that the runbook describes promotion as the moment the updater sees a release, which was never true: what it watched was the absence of the pre-release flag.

What changed

It asks /releases/latest, which answers the question directly. That endpoint returns the release marked latest and nothing else, excludes drafts and pre-releases by definition, and 404s while every release is a pre-release, which is the correct answer to "is there an update" in exactly the window between publishing and promoting.

The draft and prerelease checks stay, as belt and braces rather than as the decision, so a change in that endpoint's behaviour leaves the app quiet rather than inheriting it.

This reverses a deliberate choice recorded in the old comment, that the decision belonged in code rather than in GitHub's endpoint semantics. That reasoning is what changes: "whatever GitHub calls latest" is the rule we actually want.

Tests

Six, where there were none. Two fail without this change, including the one that needs both endpoints stubbed to express the bug at all: the unpromoted release is absent from /releases/latest and present in the list.

The rest pin the cases that must stay silent: a pre-release, a draft, a 404 while nothing is promoted, and the running version matching the latest one.

stubUpdateCheck in helpers.mjs now stubs a single object rather than an array, and its comment no longer says the app polls the list.

Verified

Playwright 195 passing, node --check over every module, i18n coverage clean.

GitHub's publish dialog has three states, not two: set as the latest release,
set as a pre-release, or neither. The updater could only tell two of them
apart, and treated the third as latest.

It read the list endpoint and took the first release that was neither a draft
nor a pre-release. A release published with nothing ticked comes back as
`draft: false, prerelease: false`, indistinguishable from a promoted one by any
field the list carries, so it was offered to every install.

That is not a fault anyone hits by following the release runbook, which
publishes as a pre-release and promotes afterwards. It is a fault that turns
one slip in the publish dialog into a build shipped to everybody, and the
runbook itself describes promotion as the thing the updater watches, which was
never true: what it watched was the absence of the pre-release flag.

So it asks /releases/latest, which answers the question directly. That endpoint
returns the release marked latest and nothing else, excludes drafts and
pre-releases by definition, and 404s while every release is a pre-release,
which is the correct answer to "is there an update" in exactly the window
between publishing and promoting.

The draft and pre-release checks stay as belt and braces rather than as the
decision, so a change in that endpoint's behaviour leaves the app quiet instead
of inheriting it.

Six tests, where there were none. Two of them fail without this change,
including the one that needs both endpoints stubbed to express the bug at all:
the unpromoted release is absent from /releases/latest and present in the list.
@thcp
thcp merged commit 7f38a3e into main Sep 22, 2026
10 checks passed
@thcp
thcp deleted the fix/update-only-latest-666 branch September 22, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A release published as "none" is offered to every user as an update

1 participant