Problem
Two latent footguns in the release pipeline, surfaced while getting the 1.2.0 release working end to end. Neither bit this time, but both can cause a silent regression on a future release.
1. No downgrade guard on the Homebrew formula
scripts/release-helper.sh regenerates the formula for whatever version it is handed, with no "only if newer" check. Because release.yml accepts a workflow_dispatch version input and also fires on any pushed v* tag, re-running the release for an older tag (e.g. dispatching v1.1.0) would rewrite the tap formula backwards, downgrading everyone's brew upgrade to the older version. There is nothing stopping an accidental re-dispatch of an old tag from doing this.
Related: gh release create in the same job auto-marks the most recently created release as "Latest", so creating an older release can also steal the Latest pointer from the current version, which install.sh resolves via releases/latest.
2. crates.io publish failures are silent
The crates job is continue-on-error: true (.github/workflows/release.yml). That was the right call when crates.io was best-effort and unproven, but now that the crate is live and expected to track every release, a failed publish (expired token, transient registry error, a version that fails cargo publish --locked) goes green and crates.io silently falls behind the tags and the Homebrew tap, with no signal.
Evidence
scripts/release-helper.sh — no version comparison anywhere; writes asset_url for the passed $VERSION unconditionally
.github/workflows/release.yml — crates job continue-on-error: true; release job creates the GitHub Release without forcing --latest=false
- History confirming the shape: crates.io currently has
1.0.0 and 1.2.0 but not 1.1.0 (the 1.1.0 release was cancelled mid-matrix and its publish was skipped, unnoticed)
Proposed fix
- Guard the Homebrew formula update so it only advances the version (compare against the formula's current version, or against the tag being the highest), and skip with a clear log line otherwise.
- Decide the Latest-pointer policy explicitly (e.g. pass
--latest only when the tag is the newest semver).
- Keep
crates non-fatal to the binary release, but surface a failure instead of swallowing it — a step that checks the publish result and emits a ::warning::/::error:: annotation, or a notification, so a silent crates.io drift cannot happen again.
Acceptance
Context
Companion to the hardening in #87 (retire macos-13, decouple crates from the binary matrix, 6-binary check, timeouts). Those changes made the pipeline complete a run; these are the remaining reliability edges.
Problem
Two latent footguns in the release pipeline, surfaced while getting the 1.2.0 release working end to end. Neither bit this time, but both can cause a silent regression on a future release.
1. No downgrade guard on the Homebrew formula
scripts/release-helper.shregenerates the formula for whatever version it is handed, with no "only if newer" check. Becauserelease.ymlaccepts aworkflow_dispatchversion input and also fires on any pushedv*tag, re-running the release for an older tag (e.g. dispatchingv1.1.0) would rewrite the tap formula backwards, downgrading everyone'sbrew upgradeto the older version. There is nothing stopping an accidental re-dispatch of an old tag from doing this.Related:
gh release createin the same job auto-marks the most recently created release as "Latest", so creating an older release can also steal the Latest pointer from the current version, whichinstall.shresolves viareleases/latest.2. crates.io publish failures are silent
The
cratesjob iscontinue-on-error: true(.github/workflows/release.yml). That was the right call when crates.io was best-effort and unproven, but now that the crate is live and expected to track every release, a failed publish (expired token, transient registry error, a version that failscargo publish --locked) goes green and crates.io silently falls behind the tags and the Homebrew tap, with no signal.Evidence
scripts/release-helper.sh— no version comparison anywhere; writesasset_urlfor the passed$VERSIONunconditionally.github/workflows/release.yml—cratesjobcontinue-on-error: true;releasejob creates the GitHub Release without forcing--latest=false1.0.0and1.2.0but not1.1.0(the 1.1.0 release was cancelled mid-matrix and its publish was skipped, unnoticed)Proposed fix
--latestonly when the tag is the newest semver).cratesnon-fatal to the binary release, but surface a failure instead of swallowing it — a step that checks the publish result and emits a::warning::/::error::annotation, or a notification, so a silent crates.io drift cannot happen again.Acceptance
Context
Companion to the hardening in #87 (retire macos-13, decouple crates from the binary matrix, 6-binary check, timeouts). Those changes made the pipeline complete a run; these are the remaining reliability edges.