Skip to content

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

Description

@thcp

What goes wrong

GitHub's publish dialog offers three states: set as the latest release, set as a pre-release, or neither. The in-app updater treats that third state, "none", exactly like latest, so a release published without ticking anything is pushed to every install.

Why

checkForUpdate in static/js/catalog.js fetches the ten newest releases and takes the first one that passes:

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

Two flags, and latest is not one of them. A release published with neither box ticked comes back as draft: false, prerelease: false, matches, and is offered.

Why it matters more than it looks

The release runbook says promotion, "Set as the latest release", is the moment a build becomes visible to the updater. That is the wrong mental model, and it is the one written down. What the updater actually watches is the absence of the pre-release flag. The two agree as long as every release is published as a pre-release first and promoted afterwards, and they disagree the moment anyone publishes a release as "none" believing it is invisible.

So this is not a fault anyone would hit by following the process. It is a fault that turns one slip in the publish dialog into a release shipped to everybody.

What it should do

Offer a release only when GitHub itself marks it as the latest one. Pre-release and "none" should both be silent.

Constraints for whoever fixes it

  • /releases/latest is the endpoint that answers this: it returns exactly the release GitHub considers latest and excludes drafts and pre-releases by definition. The current code deliberately avoids it, with a comment explaining that the choice belongs in code rather than in GitHub's endpoint semantics. That reasoning is what changes here: "whatever GitHub calls latest" becomes the rule we actually want.
  • The REST list endpoint does not expose a per-release "is latest" flag, so filtering the list cannot answer this. It has to be the other endpoint.
  • stubUpdateCheck in tests/e2e/helpers.mjs stubs an array and its comment states the app polls the list rather than /releases/latest. That stub and its comment both change with this.
  • Worth keeping a defensive !draft && !prerelease check even against that endpoint, so the app does not depend solely on the endpoint's behaviour staying as documented.

Activity

  1. added
    bugSomething isn't working
    on Sep 22, 2026
  2. self-assigned this
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions