Skip to content

Release: keep prereleases off the auto-update feed and Homebrew cask#468

Closed
FuJacob wants to merge 1 commit into
mainfrom
harden-release-prerelease-channel
Closed

Release: keep prereleases off the auto-update feed and Homebrew cask#468
FuJacob wants to merge 1 commit into
mainfrom
harden-release-prerelease-channel

Conversation

@FuJacob
Copy link
Copy Markdown
Owner

@FuJacob FuJacob commented May 31, 2026

Summary

Review of release.yml found the stable release path solid, but the prerelease path had a material gap: a prerelease tag (v1.2.3-beta) ran the full publish flow, which overwrote the production Sparkle appcast at updates.cotabby.app/appcast.xml and bumped the Homebrew cask. Because every user sits on Sparkle's default channel (single SUFeedURL, no allowedChannels opt-in anywhere) and the appcast carries no <sparkle:channel>, that beta would be force-offered to all auto-update users and become the brew install target. The --prerelease flag only controls GitHub's "Latest" badge, not Sparkle.

This makes prereleases download-only: they still create a GitHub prerelease (and still build + notarize + sign + generate the appcast as an artifact, so signing is validated), but they no longer publish the appcast to Pages or bump the cask. The stable release path is behaviorally unchanged: every new gate is is_prerelease != 'true', which is always true for a stable tag.

Also canonicalizes the appcast enclosure repo name (Cotabby -> cotabby) so the signed download URL no longer depends on GitHub's mixed-case redirect surviving a future rename/transfer.

Changes:

  • release.yml: gate the four Pages steps (Prepare/Configure/Upload/Deploy) and the update-homebrew-cask job to stable releases; make the run summary report the appcast as "not published (prerelease)" instead of printing a feed URL that was not updated.
  • generate_appcast.py: DEFAULT_REPOSITORY -> cotabby.

Not changed, by decision:

  • sparkle:minimumSystemVersion is still omitted. The app requires macOS 14, so there is no <14 population to mis-offer; a hardcoded value would only add a drift coupling for ~zero safety.
  • No <sparkle:channel> / allowedChannels beta channel was added. That is a larger product feature (opt-in beta track); this PR just stops betas from leaking to stable users. If you later want a real beta channel, that is the follow-up.

Validation

python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"   # YAML OK, 4 jobs intact
python3 -m py_compile scripts/generate_appcast.py                                  # OK

Confirmed: Create GitHub Release and Generate signed appcast remain ungated (run for both channels); the four Pages steps and the Homebrew job are gated to stable; enclosure URL renders as https://github.com/FuJacob/cotabby/releases/download/v1.2.3/Cotabby.dmg.

A release workflow can only be fully exercised by cutting a tag, so this was validated by YAML/Python parse and gating inspection rather than an end-to-end run.

Linked issues

(none filed)

Risk / rollout notes

  • Stable releases: no behavior change (all gates evaluate true).
  • Prereleases: now download-only. Pushing a -beta tag creates a GitHub prerelease but does not move the auto-update feed or the cask. This is the intended fix.
  • Because release infra cannot be dry-run end-to-end, recommend watching the next prerelease tag (if any) to confirm the Pages/Homebrew steps skip as expected.

Greptile Summary

This PR closes the gap where a prerelease tag (v1.2.3-beta) ran the full publish flow, overwriting the production Sparkle appcast and bumping the Homebrew cask. Prereleases now create a GitHub release and retain the signed appcast as an artifact for signing validation, but skip the Pages deploy and Homebrew dispatch.

  • .github/workflows/release.yml: four GitHub Pages steps and the update-homebrew-cask job are gated with is_prerelease != 'true'; the run summary now reports the appcast as "not published (prerelease)" instead of a URL that was not updated.
  • scripts/generate_appcast.py: DEFAULT_REPOSITORY changed from "Cotabby" to "cotabby" to canonicalize the enclosure URL in signed appcasts, removing dependence on GitHub's mixed-case redirect.

Confidence Score: 5/5

Safe to merge — stable release behavior is unchanged; prereleases correctly skip the Pages deploy and Homebrew dispatch.

The gating logic is straightforward: is_prerelease is deterministically set to the string "true" or "false" in the metadata step, and every new if: condition compares against that string using the same expression syntax already used throughout the rest of the workflow. The update-homebrew-cask job condition correctly extends the pre-existing publish == 'true' guard. The generate_appcast.py change is a single-constant rename with no logic impact. No pre-existing paths are altered.

No files require special attention.

Important Files Changed

Filename Overview
.github/workflows/release.yml Gates four Pages steps and the Homebrew cask job to stable releases via is_prerelease != 'true'; adds a conditional summary line reporting the appcast as unpublished for prereleases. Logic, expression syntax, and job dependency ordering are all correct.
scripts/generate_appcast.py Canonicalizes DEFAULT_REPOSITORY to lowercase cotabby, eliminating reliance on GitHub's case-redirect for the enclosure URL baked into signed appcasts. No logic changes; the rest of the script is unchanged.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Tag push / workflow_dispatch] --> B[build job]
    B -->|is_prerelease = version contains '-'| C{is_prerelease?}
    B --> D[notarize job]
    D --> E[publish job]
    E --> F[Create GitHub Release]
    E --> G[Generate signed appcast artifact]
    C -->|true / prerelease| H[Summary: appcast not published]
    C -->|false / stable| I[Prepare Pages artifact]
    I --> J[Configure GitHub Pages]
    J --> K[Upload Pages artifact]
    K --> L[Deploy GitHub Pages\nupdates.cotabby.app/appcast.xml]
    E --> M{publish == 'true'\nAND is_prerelease != 'true'?}
    M -->|yes| N[update-homebrew-cask job\ndispatch to FuJacob/homebrew-cotabby]
    M -->|no - prerelease or dry-run| O[skip cask update]
    F -.->|both paths| H
    G -.->|both paths| H
Loading

Reviews (1): Last reviewed commit: "Release: keep prereleases off the auto-u..." | Re-trigger Greptile

@FuJacob FuJacob closed this May 31, 2026
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.

1 participant