A release is a shipment, not a merge: make the release workflow manually triggered #115

Closed
opened 2026-08-19 02:22:12 +00:00 by logan · 1 comment
Collaborator

Report

Eight releases in twenty-two hours — v0.0.1 through v0.3.1 — for one
session's work. Each one fans out to four publishing workflows
(arch-package, homebrew-formula, android-apk, desktop-assets) on
a runner with capacity 1, so ~40 packaging jobs were booked to ship
three issues, and ordinary PR CI queued behind them repeatedly.

The version numbers are not wrong — semantic-release is doing exactly
what the commit grammar tells it — but a version per merged PR is a
version per unit of work, not per shipment, and pacman, Homebrew and
Obtainium all see every one.

Findings

  • release.yml triggers on push: branches: [main], so the trigger is
    "a PR merged" and nothing else decides.
  • semantic-release already batches. It reads every Conventional Commit
    since the last tag, so five fixes and two feats become one
    minor release with all seven in the notes. Release frequency is purely
    how often the trigger fires; nothing else needs to change to get
    batching.
  • release.yml already carries workflow_dispatch beside the push
    trigger, and the checkout step pins to github.sha, which on a
    dispatch is the head of the selected branch. So the manual path
    already works and is already exercised.

A beta integration branch was considered and rejected. It
relocates the trigger rather than removing one, and costs three things
the trigger change does not:

  • A second protected branch. main carries enable_push: false, an
    empty push whitelist and required CI / check* + CI / e2e*. PRs
    would land on beta, so that is where the checks have to be — two
    protection configs to keep in step, which is the duplication problem
    make lint's tag sets are already an instance of.
  • More queueing, not less, which is the actual complaint: every
    change would get a full check + e2e run on its PR and another on
    the betamain merge, ~20 minutes each on a one-at-a-time runner.
  • main becomes a branch nobody develops on, so the batched merge is
    where any surprise lands — in the release path, all at once.

Direction

Drop the push trigger; keep workflow_dispatch.

 on:
-  push:
-    branches: [main]
   workflow_dispatch:

This is the rule index-artifact.yml already states and is an instance
of: a job that mutates state which cannot be rebuilt in ten minutes is
triggered deliberately, not by a push.
A release is a tag, a Gitea
release, an Arch package, a Homebrew formula, a signed APK and desktop
assets, consumed by three package managers — and this repo already
documents that an Android version going backwards costs the user their
library.

A dry-run input is worth adding with it: the point of a manual trigger
is deliberateness, and deliberate means being able to see what comes out
before pulling the lever. make release-dry answers this locally
already; the same answer from the workflow costs six lines.

Deliberately not doing: a schedule. A cron would batch without
anyone remembering to, but it puts the decision back on a timer, which
is what this removes.

One trap to leave written down, since the prerelease option is what
someone will reach for next: all four publishers trigger on
push: tags: ["v*"], and v* matches v0.4.0-beta.1. They guard
v0.0.0 explicitly and nothing else, so a prerelease: true channel
would push beta builds to pacman, the Homebrew tap and the
Obtainium-facing APK registry on the first tag.

**Report** Eight releases in twenty-two hours — `v0.0.1` through `v0.3.1` — for one session's work. Each one fans out to four publishing workflows (`arch-package`, `homebrew-formula`, `android-apk`, `desktop-assets`) on a runner with **capacity 1**, so ~40 packaging jobs were booked to ship three issues, and ordinary PR CI queued behind them repeatedly. The version numbers are not wrong — `semantic-release` is doing exactly what the commit grammar tells it — but a version per merged PR is a version per *unit of work*, not per *shipment*, and pacman, Homebrew and Obtainium all see every one. **Findings** - `release.yml` triggers on `push: branches: [main]`, so the trigger is "a PR merged" and nothing else decides. - `semantic-release` already batches. It reads every Conventional Commit since the last tag, so five `fix`es and two `feat`s become **one** minor release with all seven in the notes. Release frequency is purely how often the trigger fires; nothing else needs to change to get batching. - `release.yml` already carries `workflow_dispatch` beside the push trigger, and the checkout step pins to `github.sha`, which on a dispatch is the head of the selected branch. So the manual path already works and is already exercised. **A `beta` integration branch was considered and rejected.** It relocates the trigger rather than removing one, and costs three things the trigger change does not: - A **second protected branch**. `main` carries `enable_push: false`, an empty push whitelist and required `CI / check*` + `CI / e2e*`. PRs would land on `beta`, so that is where the checks have to be — two protection configs to keep in step, which is the duplication problem `make lint`'s tag sets are already an instance of. - **More queueing, not less**, which is the actual complaint: every change would get a full `check` + `e2e` run on its PR *and* another on the `beta` → `main` merge, ~20 minutes each on a one-at-a-time runner. - `main` becomes a branch nobody develops on, so the batched merge is where any surprise lands — in the release path, all at once. **Direction** Drop the push trigger; keep `workflow_dispatch`. ```yaml on: - push: - branches: [main] workflow_dispatch: ``` This is the rule `index-artifact.yml` already states and is an instance of: *a job that mutates state which cannot be rebuilt in ten minutes is triggered deliberately, not by a push.* A release is a tag, a Gitea release, an Arch package, a Homebrew formula, a signed APK and desktop assets, consumed by three package managers — and this repo already documents that an Android version going backwards costs the user their library. A dry-run input is worth adding with it: the point of a manual trigger is deliberateness, and deliberate means being able to see what comes out before pulling the lever. `make release-dry` answers this locally already; the same answer from the workflow costs six lines. **Deliberately not doing:** a schedule. A cron would batch without anyone remembering to, but it puts the decision back on a timer, which is what this removes. **One trap to leave written down**, since the prerelease option is what someone will reach for next: all four publishers trigger on `push: tags: ["v*"]`, and **`v*` matches `v0.4.0-beta.1`**. They guard `v0.0.0` explicitly and nothing else, so a `prerelease: true` channel would push beta builds to pacman, the Homebrew tap and the Obtainium-facing APK registry on the first tag.
logan added the Area/Packaging
Priority
High
2
Kind/Enhancement
Reviewed
Confirmed
1
labels 2026-08-19 02:22:12 +00:00
logan self-assigned this 2026-08-19 02:22:19 +00:00
logan added the
Status
In Progress
label 2026-08-19 02:22:20 +00:00
Author
Collaborator

Claiming. Branch: ci/115-manual-release. Implementing the Direction as written: drop the push trigger, keep workflow_dispatch, add a dry-run input, and add the v* prerelease guard to the four publishers so the trap above is closed rather than only documented.

Claiming. Branch: `ci/115-manual-release`. Implementing the Direction as written: drop the push trigger, keep `workflow_dispatch`, add a dry-run input, and add the `v*` prerelease guard to the four publishers so the trap above is closed rather than only documented.
logan closed this issue 2026-08-19 02:36:01 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-19 02:36:14 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#115