Skip to content

fix(CI): Publish release binaries via draft releases - #19

Merged
almostintuitive merged 1 commit into
mainfrom
fix/release-binaries-and-install-verification
Aug 27, 2026
Merged

almostintuitive merged 1 commit into
mainfrom
fix/release-binaries-and-install-verification

Conversation

@almostintuitive

@almostintuitive almostintuitive commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Problem

curl -fsSL .../install.sh | bash installs a 9-byte text file containing Not Found as /usr/local/bin/aperiodic:

$ file /usr/local/bin/aperiodic
/usr/local/bin/aperiodic: ASCII text, with no line terminators
$ aperiodic --version
/usr/local/bin/aperiodic: line 1: Not: command not found

Every release since v1.0.4 has shipped with zero binaries, so releases/latest/download/aperiodic-darwin-arm64 404s:

tag assets
v1.0.5 none
v1.0.4 none
v1.0.3 6 binaries

Root cause

Three failures compounding:

  1. ci.yml's release job has never run. bump.yml pushes the tag with the default GITHUB_TOKEN, and GitHub deliberately does not trigger workflows from GITHUB_TOKEN events. Every ci.yml run in this repo's history is event: push on main or pull_request — not one tag push.

  2. Immutable releases reject post-publish uploads. With no automated release, tags were published by hand, firing release-assets.yml on release: published. That's too late — run 32262281202 built all six binaries and then died on:

    Cannot upload asset aperiodic-linux-arm64 to an immutable release.
    GitHub only allows asset uploads before a release is published.
    

    fix(CI): stop re-uploading release assets to already-published releases #18 responded by deleting the upload logic rather than moving it before publication, so nothing has uploaded binaries since.

  3. install.sh failed silently. It used curl -L without -f, so curl exits 0 on a 404 and writes the error body to disk. The script then chmod +x'd it and moved it into /usr/local/bin.

Changes

bump.yml now owns the entire release. Tag → build all six targets → attach them to a draft release (permitted under immutability) → verify the asset count → publish. Keeping it in one workflow sidesteps the GITHUB_TOKEN trigger problem entirely, with no PAT required.

The asset-count check means a partial upload fails the run instead of quietly publishing an empty release — the exact failure mode that went unnoticed for two releases.

ci.yml's release job is removed. It could never fire from a GITHUB_TOKEN tag push, and on a manual tag push it would now race bump.yml and fail on immutability regardless.

install.sh fails loudly. curl -fL, plus explicit rejection of empty and HTML/error-page responses, and temp-file cleanup on every exit path. INSTALL_DIR is now overridable, which makes the happy path testable without touching the system.

PagerDuty

Adds a notify-failure job, on ubuntu-latest to match this repo's runners. A silent release failure is precisely what this bug was.

Verification

install.sh against the currently-broken latest — now exits 1 and leaves the system untouched:

Error: Failed to download binary from https://github.com/.../aperiodic-darwin-arm64
The release may not exist, or it may not have assets for darwin-arm64.
EXIT CODE: 1

Happy path against v1.0.3, which still has assets:

$ INSTALL_DIR=/tmp/testbin bash install.sh v1.0.3
Installation complete.
$ file /tmp/testbin/aperiodic
/tmp/testbin/aperiodic: Mach-O 64-bit executable arm64

actionlint clean across all workflows. go build ./... passes; go test ./... gives 33 passed / 5 failed, all five being pre-existing integration tests gated on APERIODIC_API_KEY, which CI supplies.

Follow-ups (not in this PR)

  • v1.0.4 and v1.0.5 stay assetless — immutable releases can't be amended. The next bump run produces the first working release; until then install.sh correctly refuses rather than installing garbage.
  • release-assets.yml (the README link updater) triggers on release: published. Since bump.yml publishes via GITHUB_TOKEN, it won't fire either — same root cause as feature(Project): Add Go CLI downloader with tests and GitHub Actions release workflow #1, worth folding into bump.yml separately.

🤖 Generated with Claude Code

Releases have shipped without binaries since v1.0.4, so
`install.sh` has been installing a 404 body as the CLI.

Three compounding causes:

1. `bump.yml` pushes the version tag with the default
   `GITHUB_TOKEN`. GitHub does not trigger workflows from
   `GITHUB_TOKEN` events, so `ci.yml`'s tag-gated `release`
   job never ran — not once, for any tag.
2. Releases were then published by hand, which fired
   `release-assets.yml` on `release: published`. This repo has
   immutable releases enabled, so uploading assets after
   publication is rejected: "Cannot upload asset ... to an
   immutable release."
3. `install.sh` downloaded with `curl -L` (no `-f`), so curl
   exited 0 on the resulting 404 and wrote the response body to
   disk. The script then chmod +x'd a 9-byte file containing
   "Not Found" and moved it to /usr/local/bin/aperiodic.

`bump.yml` now owns the whole release: it tags, builds all six
targets, attaches them to a *draft* release (allowed under
immutability), verifies the asset count, and only then flips the
release to published.

The `release` job in `ci.yml` is removed. It could never fire
from a `GITHUB_TOKEN` tag push, and on a manual tag push it
would now race `bump.yml` and fail on immutability anyway.

`install.sh` now uses `curl -fL`, rejects empty or HTML/error
responses, and cleans up its temp file on any exit path.

Also wires up PagerDuty on release failure, matching the
notify-failure pattern from dream-faster/unravel-router#517.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@almostintuitive
almostintuitive merged commit cdb2ecb into main Aug 27, 2026
9 checks passed
@almostintuitive
almostintuitive deleted the fix/release-binaries-and-install-verification branch August 27, 2026 10:41
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