Skip to content

release: derive the install text from signing state, fix the changelog link (closes #443) - #444

Merged
benmcollins merged 1 commit into
mainfrom
443-release-body
Aug 22, 2026
Merged

benmcollins merged 1 commit into
mainfrom
443-release-body

Conversation

@benmcollins

Copy link
Copy Markdown
Owner

Closes #443.

The install boilerplate in release.yml's Publish release step was hardcoded and has been wrong on every release since v1.1.0 — whose own tag annotation already read "macOS bundles are now both signed and notarized". Three defects, all visible on the v1.4.1 release page.

1. The macOS line was false

Verified against the published QuMesh-1.4.1-macos.dmg:

$ codesign -dv --verbose=4 QuMesh.app
Authority=Developer ID Application: maClara, LLC (53R32TQWB6)
CodeDirectory v=20500 flags=0x10000(runtime)
Timestamp=Aug 22, 2026 at 5:16:59 AM

$ spctl -a -vvv -t exec QuMesh.app
QuMesh.app: accepted
source=Notarized Developer ID

$ xcrun stapler validate QuMesh-1.4.1-macos.dmg
The validate action worked!

Telling users to right-click → Open past a Gatekeeper prompt that never appears is worse than saying nothing at all.

Both platform lines now derive from whether the signing secrets are provisioned:

MAC_NOTARIZED: ${{ secrets.MAC_NOTARIZE_APPLE_ID != '' }}
WIN_SIGNED: ${{ secrets.WIN_SIGN_THUMBPRINT != '' }}

Accurate by construction: the Notarize step has no continue-on-error, so with the secret set the macOS job fails before publish if notarization fails. Windows is genuinely unsigned (WIN_SIGN_THUMBPRINT is empty), so its SmartScreen wording stays and self-corrects when a cert is provisioned — the two platforms have diverged and the text has to say so rather than being flipped wholesale.

2. The changelog link 404s

compare/...$TAG has no base ref → compare/...v1.4.1 → 404. Now from gh release view --json tagName (with no argument that resolves the latest release, which at publish time is still the previous one), and dropped entirely when there is none. Deliberately not git describe: this job checks out at depth 1 with no tag history.

3. The release body never contained the release notes

app/release-notes/<TAG>.md fed only the Sparkle appcast <description>. So in-app updaters showed the full notes while the Releases page — where people land from a link — showed install instructions, a broken changelog link, and nothing about what changed. The source-precedence block is now resolved once into $NOTES_MD_FILE that both consumers read, so they cannot drift apart again.

Tests

Body assembly moves to .github/scripts/release-body.sh so it can be tested. release.yml only fires on v*.*.* tags, so nothing in CI ever exercised this text — precisely how it stayed wrong across five releases. New release-body job on every PR runs 22 assertions:

  • both signing states per platform, asserting the wrong claim is absent as well as the right one present
  • changelog link with a previous tag, with none, and with prev == current
  • leading # QuMesh <version> stripped (GitHub already renders the title) while ## sub-headings survive, and a notes file with no H1 keeps its first line
  • the APT block staying literal — ${ID}${VERSION_ID} unexpanded, trailing backslashes intact

Verified to fail (3 of 22) when the old hardcoded macOS text is restored. actionlint reports no new findings on either workflow; shellcheck is clean on both scripts.

Verification

Dry-ran the full Resolve release notes → Publish release chain locally against app/release-notes/v1.4.1.md with a stubbed gh, producing the 150-line body the next release will publish.

Follow-ups, not in this PR

  • The notarization ticket is stapled to the .dmg but not to QuMesh.app inside it, so a user who copies the app out and first launches it offline falls back to an online notarization check.
  • The v1.4.1 release body itself still carries the old text; that is a one-off gh release edit once this merges, not a workflow change.

…g link (closes #443)

The install boilerplate in release.yml's Publish step was hardcoded and
had drifted from reality on every release since v1.1.0 — whose own tag
annotation already read "macOS bundles are now both signed and
notarized". The published body still said:

    On first launch Gatekeeper will warn that the bundle is unsigned;
    right-click → Open to bypass.

Verified against the shipped QuMesh-1.4.1-macos.dmg, that is false:

    Authority=Developer ID Application: maClara, LLC (53R32TQWB6)
    CodeDirectory flags=0x10000(runtime)
    spctl -a -t exec: accepted, source=Notarized Developer ID
    xcrun stapler validate: The validate action worked!

Telling users to bypass Gatekeeper on a bundle that passes it is worse
than saying nothing. Both platform lines now derive from whether the
signing secrets are provisioned. That is accurate by construction: the
Notarize step has no continue-on-error, so with MAC_NOTARIZE_APPLE_ID
set the macOS job fails before publish if notarization fails. Windows
is genuinely unsigned (WIN_SIGN_THUMBPRINT is empty), so its
SmartScreen wording stays and self-corrects if a cert is provisioned —
the platforms have diverged and the text has to say so rather than
being flipped wholesale.

Two more defects in the same heredoc:

The changelog link never had a base ref. `compare/...$TAG` renders as
`compare/...v1.4.1`, which is a 404; no previous tag was ever computed.
It now comes from `gh release view --json tagName` — with no argument
that resolves the latest release, still the previous one at publish
time — and the line is dropped entirely when there is none. Not `git
describe`: this job checks out at depth 1 with no tag history.

And the release body never contained the release notes. app/release-
notes/<TAG>.md fed only the Sparkle appcast, so in-app updaters showed
the full notes while the Releases page — where people land from a link
— showed install instructions, a broken changelog link, and no
description of what changed. The source-precedence block is now
resolved once into $NOTES_MD_FILE that both consumers read.

Body assembly moves to .github/scripts/release-body.sh so it can be
tested. release.yml only fires on v*.*.* tags, so nothing in CI ever
exercised this text — which is precisely how it stayed wrong across
five releases. test-release-body.sh covers 22 assertions (both signing
states per platform, the three changelog cases, H1 stripping, and the
APT block staying literal) and runs on every PR. Verified to fail when
the old hardcoded macOS text is restored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@benmcollins
benmcollins merged commit 30155b4 into main Aug 22, 2026
11 checks passed
@benmcollins
benmcollins deleted the 443-release-body branch August 22, 2026 09:44
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.

release: GitHub Release body claims the macOS bundle is unsigned, links a 404 changelog, and omits the release notes

1 participant