From c15549872f923b579bce6a9744af0602fc629963 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:04:01 +0200 Subject: [PATCH] ci: stop at the draft; publishing is a human action (#719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2.4.3-beta.2 was cut by tag and came out correctly — published, pre-release, zip attached, notes from CHANGELOG.md — and then nothing else happened. No updater registration, no Docker images. GitHub does not start workflow runs from events created with GITHUB_TOKEN. The publish step authenticated as github-actions[bot], so `release: published` fired and triggered nothing. Every earlier docker.yaml run was event=release from a release a human published, which is why beta.1 worked and beta.2 did not. The workflow now stops at the draft. Everything else is unchanged: the tag still runs the tests, reads the notes, builds the package and attaches it. Pressing Publish fires the event under a real identity and the proven downstream runs as it always has — and it puts a deliberate gate in front of a release going public. prerelease and make_latest move onto the draft rather than being applied at publish time, so the flags are already right when the button is pressed; GitHub's publish dialog otherwise defaults "Set as the latest release" to checked, which would let a 3.0.0 alpha displace 2.4.x. The run now ends by writing the draft URL and the resolved flags to the job summary, since a draft nobody knows about is no use. Documents the whole flow in the agent guide, including why publishing is manual — the reasoning is not guessable from the workflow alone. --- .github/workflows/release.yaml | 40 ++++++++++++++++++++++++---------- CLAUDE.md | 36 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 21dbbf6b..d9ef82de 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -72,26 +72,44 @@ jobs: # have passed and the asset is in place, so downstream registration can # never race the upload — and a failed run leaves no release at all rather # than a published one nobody can download. + # The draft is where this workflow stops. Publishing is left to a person, + # because a release published by the workflow would never reach docker.yaml: + # GitHub does not start workflow runs from events created with GITHUB_TOKEN, + # so `release: published` fires as github-actions[bot] and triggers nothing. + # 2.4.3-beta.2 was published that way and got no registration and no images. + # A human pressing Publish fires the event under their own identity, and the + # existing downstream runs untouched. + # + # prerelease and make_latest are set here rather than at publish time, so the + # release is already correct when that button is pressed — GitHub's publish + # dialog otherwise defaults "Set as the latest release" to checked, which + # would let a 3.0.0 alpha displace 2.4.x. - name: Create the draft release + id: draft uses: softprops/action-gh-release@v3 with: files: InvoiceShelf.zip body_path: /tmp/notes.md draft: true prerelease: ${{ contains(github.ref_name, '-') }} + make_latest: ${{ !contains(github.ref_name, '-') && startsWith(github.ref_name, format('{0}.', env.LATEST_MAJOR)) }} - - name: Publish it + # A draft nobody knows about is no use, so the run ends by saying what was + # built and what to do with it. + - name: Say what to do next env: - GH_TOKEN: ${{ github.token }} TAG: ${{ github.ref_name }} - # GitHub's "Latest release" pointer, gated exactly as docker.yaml gates - # its moving image tags — so a 3.0.0 alpha cannot displace 2.4.x as the - # release users are shown first while 2.x is still the stable line. + URL: ${{ steps.draft.outputs.url }} + PRERELEASE: ${{ contains(github.ref_name, '-') }} IS_LATEST: ${{ !contains(github.ref_name, '-') && startsWith(github.ref_name, format('{0}.', env.LATEST_MAJOR)) }} run: | - if [ "$IS_LATEST" = "true" ]; then - gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest - else - gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest=false - fi - echo "Published $TAG (latest=$IS_LATEST)" + { + echo "### $TAG is drafted and ready to publish" + echo + echo "- Draft: $URL" + echo "- Pre-release: \`$PRERELEASE\` — a pre-release goes to the insider channel only" + echo "- Mark as latest: \`$IS_LATEST\`" + echo + echo "**Publishing it** builds the Docker images and registers it on the updater." + echo "Nothing reaches installs until you do." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/CLAUDE.md b/CLAUDE.md index ea7f605c..22130848 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,42 @@ Supports MySQL, PostgreSQL, and SQLite. Prefer Eloquent over raw queries. Use `M - Every change must have tests (feature tests preferred over unit tests) - Run `vendor/bin/pint --dirty --format agent` after modifying PHP files +## Releasing + +Releases are cut by pushing a tag. Nothing is typed into GitHub by hand. + +```bash +# 1. Add a "## " section to CHANGELOG.md and bump version.md, +# in a PR like any other change — the notes are reviewed with the code. +# 2. Once merged, tag the merge commit: +git tag 2.4.3 && git push origin 2.4.3 +``` + +`release.yaml` then runs the tests, reads the `CHANGELOG.md` section for that tag, +builds the package with `make clean dist`, and creates a **draft** release with the +zip attached. It stops there and prints the draft URL in the run summary. + +**You publish the draft yourself.** That is deliberate, not an omission: GitHub does +not start workflow runs from events created with `GITHUB_TOKEN`, so a release +published by the workflow reaches nothing downstream. Pressing Publish fires +`release: published` under your own identity, which triggers `docker.yaml` to +register the release on the updater and build the Docker images. **Until you +publish, no install is offered anything.** + +Notes on the mechanics: + +- **A tag with no `CHANGELOG.md` section fails the run** before anything is built, + so a release can never go out with empty notes. +- **`prerelease` and "mark as latest" are set on the draft**, derived from the tag: a + `-` suffix (`2.4.3-beta.1`) means pre-release, which routes it to the **insider** + channel so ordinary installs are not offered it. "Latest" is gated on + `LATEST_MAJOR` in the workflows — bump it there when 3.x becomes the stable line. +- **If registration fails**, re-run it without cutting a new release: run the + `Docker Build and Push` workflow manually with `register_tag` set to the version. + That path is idempotent and does not rebuild the Docker images. +- `.github/scripts/changelog-section.php ` prints what the updater will be + sent, so you can check the notes locally before tagging. + ## CI Pipeline GitHub Actions (`check.yaml`): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4.