Skip to content

[Fix] First release publish fails when no previous GitHub release exists - #152

Merged
mrubens merged 2 commits into
developfrom
fix/first-release-publish-gate-0knqqc4exrzh4
Jul 11, 2026
Merged

[Fix] First release publish fails when no previous GitHub release exists#152
mrubens merged 2 commits into
developfrom
fix/first-release-publish-gate-0knqqc4exrzh4

Conversation

@roomote-roomote

@roomote-roomote roomote-roomote Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Opened on behalf of Matt Rubens. Follow up by mentioning @openmote, in the web UI, or in Slack.

What changed

Both publish runs for the v0.0.2 promote failed in the "Deployment acceptance" gate because this was the first release, so no previous release exists to validate upgrades against:

  • Tag run (failing job): the baseline step captured gh api repos/…/releases/latest output with || true. With no GitHub Release yet, gh 404s but prints the JSON error body to stdout, so {"message":"Not Found",…} became the docker image tag → invalid reference format.
  • Main-channel run: the fallback pulled the pre-release main-channel image as the upgrade baseline. That image predates the release pipeline; its controller crashes on boot (requires S3_* env the current compose no longer passes), so the baseline stack could never start.

The baseline step in publish-ghcr.yml now:

  1. Resolves the previous release via releases/latest inside an if ! guard, so a failed lookup yields an empty baseline instead of leaking the error body.
  2. Treats "no previous release" (and the legacy image-less v0.0.1) as skip upgrade validationdeploy/ci/deployment-smoke.sh already handles an empty BASELINE_VERSION by validating a fresh install of the candidate instead (stack boot, health checks, Docker task launch, backup/restore all still run).
  3. Uses the same latest-release resolution for main-channel and manual-dispatch runs instead of the ancient main alias, and adds a final allowlist (''/develop/v[0-9]*) so nothing malformed can ever reach docker pull. The develop channel baseline is unchanged.
  4. Keeps the upgrade leg only when the candidate v* version is strictly newer than the resolved baseline (sort -V): a manual dispatch that re-publishes an older or identical version no longer exercises a downgrade dressed up as an upgrade, and falls back to fresh-install validation instead. main-<sha> candidates are unaffected.

A changeset (patch) is included so this ships as v0.0.3, which will be the first fully published release (images + GitHub Release). v0.0.2 remains tag-only: tag re-runs execute the workflow frozen at the tag, so it cannot be rescued by re-running.

Why this change was made

The upgrade-validation gate assumed a previous published release always exists. On the very first gated release that assumption produced two different hard failures, blocking the v0.0.2 image publish and GitHub Release entirely.

Impact

  • The first production release can pass the acceptance gate (fresh-install validation only), and every later release resumes full upgrade/rollback validation against the real previous release tag — once v0.0.3's GitHub Release exists, subsequent runs get a valid pullable baseline automatically.
  • main-channel publish runs validate against the previous release instead of a known-broken pre-release image.
  • Manual re-publishes of older versions are validated as fresh installs instead of failing (or misleadingly passing) a downgrade path.
  • Verified the exact new logic against the live GitHub API and with simulated version matrices: developdevelop; latest/main on this repo (no releases) → empty (skip); latest on a repo with releases → clean v* tag; candidate-vs-baseline ordering (v0.0.4>v0.0.3 keeps validation, v0.0.2<v0.0.5 and equal versions skip, v0.0.10>v0.0.9 sorts correctly). YAML parse, bash -n, and prettier all pass.

…ists

The deployment acceptance gate resolved its upgrade baseline from
releases/latest with `|| true`, so a 404 (no release yet) leaked the JSON
error body into the docker image tag. The main-channel fallback pulled the
pre-release main image, whose controller crashes under the current compose.

Resolve the baseline safely, skip upgrade validation when there is no
usable previous release (fresh-install validation still runs), and never
let a non-channel/non-v* string reach docker pull. Ships as v0.0.3 via
changeset so the first fully published release goes out on the fixed gate.
@roomote-roomote

roomote-roomote Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Re-review complete. The latest change resolves the earlier observation; no new code issues. See task

The new commit adds a strict "candidate must be newer than the resolved baseline" guard (sort -V). A manual re-publish of an older or equal version now skips upgrade validation and falls back to fresh-install validation instead of attempting a newer→older downgrade. I traced every release channel: develop is unchanged; first release, legacy v0.0.1, and any older-or-equal candidate all skip upgrade validation; genuine upgrades (tag latest, main, and a newer dispatch version) still validate against the previous release. The trailing allowlist still gates what can reach docker pull. bash -n passes.

  • .github/workflows/publish-ghcr.yml — manual workflow_dispatch downgrade concern resolved: re-publishing an older or equal version now skips upgrade validation via the sort -V newer-than-baseline guard, so it no longer starts a newer release stack and "upgrades" to an older candidate.

Review summary

The only change since the last review is commit 820f569, which directly addresses the prior low-severity observation. No new issues.

# Severity File Line(s) Issue
✅ Resolved .github/workflows/publish-ghcr.yml 194-209 Manual dispatch / tag re-publish of an older or equal version no longer validates a downgrade; the candidate must be strictly newer than the latest release for the upgrade leg to run, otherwise it falls back to fresh-install validation.

Minor note (informational, not blocking): the downgrade guard only engages when CANDIDATE_VERSION matches v[0-9]*. Since release tags and the dispatch input are v-prefixed by convention (and releases/latest excludes prereleases), the guarded paths are covered; a hypothetical non-v dispatch input would bypass the guard, but mixed-prefix comparisons are already out of the supported release convention.

…han the latest release

Manual dispatch can re-publish an older version while a newer release is
latest; validating baseline->candidate would then exercise a downgrade
dressed up as an upgrade. Keep the upgrade leg only when the candidate is
strictly newer than the resolved baseline (sort -V), and fall back to
fresh-install validation otherwise.
@mrubens
mrubens marked this pull request as ready for review July 11, 2026 00:12
@mrubens
mrubens merged commit 4bbe101 into develop Jul 11, 2026
@mrubens
mrubens deleted the fix/first-release-publish-gate-0knqqc4exrzh4 branch July 12, 2026 04:19
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.

2 participants