You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Establish the release process, document it, and cut v0.1.0.
.goreleaser.yaml is fully configured and nothing has ever invoked it. There are no tags, no releases, and no release workflow — ci.yml is the only workflow in the repo. So the download path, the checksum file, the archive layout and the Homebrew cask are all things this project has never done once, and every one of them is a guess until a real tag has produced real assets.
This blocks #29 (ship markfluence as a GitHub Action), whose whole premise is fetching markfluence_<version>_<os>_<arch>.tar.gz from a release. It also unblocks the two release-shaped notes already in the tree: #1 (the Homebrew tap) and #95 (SECURITY.md's supported-versions table).
What the release process has to decide
The tag must be vX.Y.Z
Git does not care, but two things here do, and both are hard requirements rather than conventions:
Go modules.github.com/mozilla/markfluence is a module and docs/github-actions.md tells people to go install github.com/mozilla/markfluence@latest. The module proxy only recognises semver tags with the v prefix — a 1.2.3 or release-1 tag is invisible to it, so @v1.2.3 does not resolve and @latest degrades to a v0.0.0-2026…-<sha> pseudo-version.
goreleaser, which parses the tag and fails with not a valid semantic version otherwise.
A prerelease suffix is fine for both: v0.1.0-rc.1 is valid semver, valid for Go, and goreleaser marks the release a prerelease on its own. Worth knowing for a dry run against a throwaway tag.
Also worth writing down because it bites downstream: .Version strips the v, so tag v1.2.3 produces markfluence_1.2.3_linux_amd64.tar.gz. Anything that constructs an asset name from a tag has to account for it.
The trigger glob is v*.*.*, not v*
The obvious tags: ['v*'] will also match a moving major tag (v1), which #29 needs. That would re-enter the workflow on every release and fail the second run on a tag goreleaser rejects as non-semver. Actions tag filters are globs rather than regex, so v*.*.* is the available way to say "three components".
The Homebrew cask needs two permissions on the release job
Settled in #1: this repository is its own tap. The cask lands in ./Casks here rather than in a separate mozilla/homebrew-markfluence, which is how mozilla/mozcloud does it, and it matters for this issue because it decides the credential. GitHub Actions' automatic GITHUB_TOKEN is scoped to the repository the workflow runs in, so a separate tap repo would have needed a fine-grained PAT someone owns and rotates; a same-repo write needs nothing extra.
What release.yml has to grant is just contents: write — which the GitHub Release itself already needs. The cask is pushed to a goreleaser/cask-<tag> branch and the release stops there; opening the PR is a human step (gh pr create), because a PR opened by the automatic GITHUB_TOKEN never triggers ci and main's ruleset requires that check with nobody able to bypass it. #1 has the measurement.
So: no separate repo to create, no pull-requests: write, and nothing here blocks on it. The one thing this issue should carry is a line in docs/releasing.md saying that the cask PR is opened by hand after the release, or the first person to cut a release will assume it failed.
No changelog file is required
There is no CHANGELOG.md and none is needed: a Release body may be empty, and .goreleaser.yaml already sets changelog: {use: github, sort: asc}, so notes are generated from commit subjects — which Conventional Commits already makes readable here. Worth a deliberate decision rather than a default, since adding one later is cheap and removing one is not.
Which platforms ship
goreleaser builds darwin+linux × arm64+amd64 — four assets as of c03b1ff. darwin/amd64 had been in ignore on the grounds that the macos-13 runner is retiring, and that came back once the repo became its own Homebrew tap: the build matrix is then the install matrix, and the cask had no on_intel block, so brew install failed on an Intel Mac. Windows is still not built and is deliberately out of scope. Recorded here because #29 turns this into a support matrix too, so it is no longer a private goreleaser detail.
Work
.github/workflows/release.yml — done in build: the release workflow, and per-platform install instructions #177. Also runs make check against the tagged commit, since a tag can be pushed at any commit. zizmor clean: actions pinned by SHA, persist-credentials: false, and the module cache disabled (cache: false has to be explicit — setup-go caches by default, so dropping cache: true left the finding standing).
Decided: draft: true plus a publish-last step — build: the release workflow, and per-platform install instructions #177. And prerelease: auto, which turned out to be required for the draft to mean anything: release.prerelease defaults to false, so an RC tag produced a non-prerelease draft that the publish step then published unconditionally, making v0.1.0-rc.1 become /releases/latest. Easy to miss, because the cask is correctly skipped for an RC (skip_upload: "auto" reads the parsed semver rather than this setting), so a rehearsal would have looked right while publishing a live release.
Dry-run against a prerelease tag — not done, and deliberately not going to be. This project does not cut prereleases. docs/releasing.md no longer recommends an RC as the rehearsal; the local snapshot build is, and it publishes nothing. release.prerelease: auto and the cask's skip_upload: "auto" stay as one-line insurance against an RC tag nobody intends to push, marked in the config as never having run (build: drop Intel macOS, and stop recommending prereleases #180).
docs/releasing.md — done as a "Cutting a release" section at the end of CONTRIBUTING.md (1f7a037), rather than a file of its own: release steps are maintainer documentation and that is where a maintainer already looks. Covers the tag format and both reasons for it, the snapshot rehearsal, the manual cask PR and why, verification, and how to recover from a failed release. It carries an IMPORTANT admonition saying the process is not usable until this issue lands — remove that admonition as part of closing this issue.
Decided: no make target.docs/releasing.md is the checklist, and a target would wrap two commands.
v0.1.0 is out. Verified: three archives (darwin_arm64, linux_arm64, linux_amd64 — Intel macOS was dropped in build: drop Intel macOS, and stop recommending prereleases #180), checksums.txt verifying, completions/ inside the archive, and the shipped binary reporting markfluence 0.1.0 (1d0a2d0, 2026-09-20) from the ldflags stamp. The cask PR merged, and brew install markfluence works.
README.md — build: the release workflow, and per-platform install instructions #177 replaced it with per-platform instructions: macOS via the cask, Linux via a release archive (no Homebrew, since Cask is macOS-only — which also settles the unverified Linux-cask question by not depending on it), and go install. The Linux snippet was verified by running it against real goreleaser archives, which caught an unqualified tar -xzf that overwrote the user's own README.md and LICENSE.
CLAUDE.md — done in fix(docs): what cutting v0.1.0 turned up #179: the trigger glob, make check against the tagged commit, draft-then-publish, prerelease: auto being required rather than default, the contents: write-only permission, and docs/releasing.md as the runbook.
Why v0.1.0 and not 1.0.0
#29 is in the 1.0.0 milestone, so the action cannot wait for the release it is meant to ship with — and the release pipeline has never run, so the first tag should be the cheap one to re-cut while it is being debugged. v0.1.0 gives #29 real assets to build against, and pre-1.0 means re-tagging costs nothing if the first attempt is wrong.
markfluence is unreleased, so there is no backwards-compatibility or migration story to design for here.
The Homebrew tap's own follow-through — Homebrew: this repository is its own tap #1: the README instructions, verifying brew install end to end after v0.1.0, and whether the cask works under Homebrew on Linux. This issue only has to give the release job the two permissions the cask PR needs.
Release provenance / attestation.checksums.txt is the integrity story for now. Worth having, and a separate change.
Windows and Intel-macOS builds. Deferred, as above.
Status after #177: the workflow, the config and the documentation are all on main. What remains is the part that can only be done by doing it — a prerelease rehearsal, then v0.1.0 — plus the CLAUDE.md note above, and deleting the two "no release yet" admonitions (README.md's Install section and docs/releasing.md's) once a release exists.
Closing
Done. v0.1.0 shipped and the whole path is exercised: tag → workflow → draft → assets → publish → cask branch → hand-opened PR → brew install.
Four things the first real release turned up, all fixed in #179 and #180:
brew trust is required before brew install from a non-official tap. Nothing in the goreleaser config could have revealed that.
The verify step downloaded into the repository root and left the files behind, and gh release download sat outside its && chain — so a failed download silently verified whatever was already there. That misfired during testing against a stale archive, printing a version that was right only by luck.
Release notes must not be hard-wrapped: GitHub renders newlines in a release body as line breaks.
Intel macOS was dropped (build: drop Intel macOS, and stop recommending prereleases #180). macOS 26 Tahoe is Apple's last Intel release, and nobody here can test on one, so the build matrix — which for a self-tapping repo is the install matrix — no longer claims it.
Establish the release process, document it, and cut
v0.1.0..goreleaser.yamlis fully configured and nothing has ever invoked it. There are no tags, no releases, and no release workflow —ci.ymlis the only workflow in the repo. So the download path, the checksum file, the archive layout and the Homebrew cask are all things this project has never done once, and every one of them is a guess until a real tag has produced real assets.This blocks #29 (ship markfluence as a GitHub Action), whose whole premise is fetching
markfluence_<version>_<os>_<arch>.tar.gzfrom a release. It also unblocks the two release-shaped notes already in the tree: #1 (the Homebrew tap) and #95 (SECURITY.md's supported-versions table).What the release process has to decide
The tag must be
vX.Y.ZGit does not care, but two things here do, and both are hard requirements rather than conventions:
github.com/mozilla/markfluenceis a module anddocs/github-actions.mdtells people togo install github.com/mozilla/markfluence@latest. The module proxy only recognises semver tags with thevprefix — a1.2.3orrelease-1tag is invisible to it, so@v1.2.3does not resolve and@latestdegrades to av0.0.0-2026…-<sha>pseudo-version.not a valid semantic versionotherwise.A prerelease suffix is fine for both:
v0.1.0-rc.1is valid semver, valid for Go, and goreleaser marks the release a prerelease on its own. Worth knowing for a dry run against a throwaway tag.Also worth writing down because it bites downstream:
.Versionstrips thev, so tagv1.2.3producesmarkfluence_1.2.3_linux_amd64.tar.gz. Anything that constructs an asset name from a tag has to account for it.The trigger glob is
v*.*.*, notv*The obvious
tags: ['v*']will also match a moving major tag (v1), which #29 needs. That would re-enter the workflow on every release and fail the second run on a tag goreleaser rejects as non-semver. Actions tag filters are globs rather than regex, sov*.*.*is the available way to say "three components".The Homebrew cask needs two permissions on the release job
Settled in #1: this repository is its own tap. The cask lands in
./Caskshere rather than in a separatemozilla/homebrew-markfluence, which is howmozilla/mozclouddoes it, and it matters for this issue because it decides the credential. GitHub Actions' automaticGITHUB_TOKENis scoped to the repository the workflow runs in, so a separate tap repo would have needed a fine-grained PAT someone owns and rotates; a same-repo write needs nothing extra.What
release.ymlhas to grant is justcontents: write— which the GitHub Release itself already needs. The cask is pushed to agoreleaser/cask-<tag>branch and the release stops there; opening the PR is a human step (gh pr create), because a PR opened by the automaticGITHUB_TOKENnever triggersciandmain's ruleset requires that check with nobody able to bypass it. #1 has the measurement.So: no separate repo to create, no
pull-requests: write, and nothing here blocks on it. The one thing this issue should carry is a line indocs/releasing.mdsaying that the cask PR is opened by hand after the release, or the first person to cut a release will assume it failed.No changelog file is required
There is no
CHANGELOG.mdand none is needed: a Release body may be empty, and.goreleaser.yamlalready setschangelog: {use: github, sort: asc}, so notes are generated from commit subjects — which Conventional Commits already makes readable here. Worth a deliberate decision rather than a default, since adding one later is cheap and removing one is not.Which platforms ship
goreleaser builds darwin+linux × arm64+amd64 — four assets as of c03b1ff.
darwin/amd64had been inignoreon the grounds that themacos-13runner is retiring, and that came back once the repo became its own Homebrew tap: the build matrix is then the install matrix, and the cask had noon_intelblock, sobrew installfailed on an Intel Mac. Windows is still not built and is deliberately out of scope. Recorded here because #29 turns this into a support matrix too, so it is no longer a private goreleaser detail.Work
.github/workflows/release.yml— done in build: the release workflow, and per-platform install instructions #177. Also runsmake checkagainst the tagged commit, since a tag can be pushed at any commit. zizmor clean: actions pinned by SHA,persist-credentials: false, and the module cache disabled (cache: falsehas to be explicit —setup-gocaches by default, so droppingcache: trueleft the finding standing).contents: writeonly, nopull-requests: write— build: the release workflow, and per-platform install instructions #177.draft: trueplus a publish-last step — build: the release workflow, and per-platform install instructions #177. Andprerelease: auto, which turned out to be required for the draft to mean anything:release.prereleasedefaults tofalse, so an RC tag produced a non-prerelease draft that the publish step then published unconditionally, makingv0.1.0-rc.1become/releases/latest. Easy to miss, because the cask is correctly skipped for an RC (skip_upload: "auto"reads the parsed semver rather than this setting), so a rehearsal would have looked right while publishing a live release.Dry-run against a prerelease tag— not done, and deliberately not going to be. This project does not cut prereleases.docs/releasing.mdno longer recommends an RC as the rehearsal; the local snapshot build is, and it publishes nothing.release.prerelease: autoand the cask'sskip_upload: "auto"stay as one-line insurance against an RC tag nobody intends to push, marked in the config as never having run (build: drop Intel macOS, and stop recommending prereleases #180).— done as a "Cutting a release" section at the end ofdocs/releasing.mdCONTRIBUTING.md(1f7a037), rather than a file of its own: release steps are maintainer documentation and that is where a maintainer already looks. Covers the tag format and both reasons for it, the snapshot rehearsal, the manual cask PR and why, verification, and how to recover from a failed release. It carries an IMPORTANT admonition saying the process is not usable until this issue lands — remove that admonition as part of closing this issue.maketarget.docs/releasing.mdis the checklist, and a target would wrap two commands.v0.1.0is out. Verified: three archives (darwin_arm64,linux_arm64,linux_amd64— Intel macOS was dropped in build: drop Intel macOS, and stop recommending prereleases #180),checksums.txtverifying,completions/inside the archive, and the shipped binary reportingmarkfluence 0.1.0 (1d0a2d0, 2026-09-20)from the ldflags stamp. The cask PR merged, andbrew install markfluenceworks.README.md— build: the release workflow, and per-platform install instructions #177 replaced it with per-platform instructions: macOS via the cask, Linux via a release archive (no Homebrew, since Cask is macOS-only — which also settles the unverified Linux-cask question by not depending on it), andgo install. The Linux snippet was verified by running it against real goreleaser archives, which caught an unqualifiedtar -xzfthat overwrote the user's ownREADME.mdandLICENSE.SECURITY.md— build: the release workflow, and per-platform install instructions #177 states the pre-1.0 policy (latest release only, no backports). The table is still SECURITY.md: replace the no-releases-yet note with a supported-versions table #95.CLAUDE.md— done in fix(docs): what cutting v0.1.0 turned up #179: the trigger glob,make checkagainst the tagged commit, draft-then-publish,prerelease: autobeing required rather than default, thecontents: write-only permission, anddocs/releasing.mdas the runbook.Why
v0.1.0and not1.0.0#29 is in the 1.0.0 milestone, so the action cannot wait for the release it is meant to ship with — and the release pipeline has never run, so the first tag should be the cheap one to re-cut while it is being debugged.
v0.1.0gives #29 real assets to build against, and pre-1.0 means re-tagging costs nothing if the first attempt is wrong.markfluence is unreleased, so there is no backwards-compatibility or migration story to design for here.
Not in scope
brew installend to end afterv0.1.0, and whether the cask works under Homebrew on Linux. This issue only has to give the release job the two permissions the cask PR needs.v1tag, and Marketplace publication. Both belong to Ship markfluence as a reusable GitHub Action #29, which is what needs them;release.ymlgrows the tag-moving step there.checksums.txtis the integrity story for now. Worth having, and a separate change.Status after #177: the workflow, the config and the documentation are all on
main. What remains is the part that can only be done by doing it — a prerelease rehearsal, thenv0.1.0— plus theCLAUDE.mdnote above, and deleting the two "no release yet" admonitions (README.md's Install section anddocs/releasing.md's) once a release exists.Closing
Done.
v0.1.0shipped and the whole path is exercised: tag → workflow → draft → assets → publish → cask branch → hand-opened PR →brew install.Four things the first real release turned up, all fixed in #179 and #180:
brew trustis required beforebrew installfrom a non-official tap. Nothing in the goreleaser config could have revealed that.gh release downloadsat outside its&&chain — so a failed download silently verified whatever was already there. That misfired during testing against a stale archive, printing a version that was right only by luck.Left open elsewhere, not blocking this: