Reflect is pre-1.0. Releases are cut by hand: a maintainer merges a release PR
that sets VERSION and the CHANGELOG.md section, then pushes the matching
v<VERSION> tag. The tag triggers
.github/workflows/release.yml, which
builds, signs and publishes the GitHub release. There is no release-please or
other bot: nothing bumps VERSION or writes CHANGELOG.md automatically.
The next release version comes from VERSION. The new Reflect
series restarts below the historical hawk versions; use explicit tags for
Go installation rather than assuming @latest selects this series.
Only repository admins can create, move or delete release tags. The
repository ruleset protect-release-tags (id 24053191) is active for
tags matching refs/tags/v* and restricts creation, update, deletion and
non-fast-forward updates, with the repository admin role as the only bypass
actor. A push of a v* tag by anyone else is rejected, so a write-access
account cannot trigger a signed release. Inspect it with:
gh api repos/GrayCodeAI/reflect/rulesets/24053191The main branch is separately protected (required status checks, no force
pushes).
-
Release PR (conventional title, e.g.
chore(release): vX.Y.Z):-
VERSIONholdsX.Y.Z(no leadingv). -
CHANGELOG.md: rename## [Unreleased]to## [X.Y.Z] — YYYY-MM-DDand add a new, empty## [Unreleased]above it. This section becomes the GitHub release notes verbatim. -
testdata/compatibility-matrix.json: set thestablematrix to reflectX.Y.Zand the relay version pinned ingo.mod. -
README.mdinstall section: point theinstall.shURL at the new tag. -
Run locally:
make release-check TAG=vX.Y.Z # tag == v$(cat VERSION), CHANGELOG section present make release-snapshot # goreleaser check + all archives into dist/, no publish/sign
-
-
Merge the PR and wait for CI on
mainto pass. -
Tag the merge commit (admins only, see above):
git fetch origin git tag -s vX.Y.Z -m "reflect vX.Y.Z" <merge-commit-sha> git push origin vX.Y.Z
-
Watch the
releaseworkflow run to completion (next section). -
Verify the published release as a user would (see Verifying a release), including
curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/reflect/vX.Y.Z/install.sh | REFLECT_REQUIRE_COSIGN=1 shandgo install github.com/GrayCodeAI/reflect/cmd/reflect@vX.Y.Z.
make release deliberately refuses to publish from a workstation: releases are
signed with the workflow's GitHub OIDC identity, which a local run cannot have.
On a pushed v* tag, with permissions: contents: write, id-token: write:
scripts/check-release-tag.shrefuses the run unless the tag is exactlyv+VERSIONandCHANGELOG.mdhas a non-empty## [<VERSION>]section; that section is extracted as the release notes.scripts/check-no-replace-directives.shrefuses localreplacedirectives. The job setsGOWORK=off, so dependencies (including relay) come only fromgo.mod/go.sumvia the public proxy.- syft (
anchore/sbom-action/download-syft) and cosign (sigstore/cosign-installer) are installed; both actions are pinned by commit SHA. - GoReleaser v2.17.0 (
.goreleaser.yml) builds and publishes:reflect_<version>_<os>_<arch>.tar.gzfor linux and darwin and.zipfor windows, each on amd64 and arm64 (six archives,-trimpath,CGO_ENABLED=0);reflect_<version>_source.tar.gz;- one SPDX SBOM per archive (
<archive>.spdx.sbom.json); checksums.txt(SHA-256 of every asset above);checksums.txt.sigstore.json, a cosign keyless Sigstore bundle (certificate, signature and transparency-log proof) overchecksums.txt.
- The workflow re-downloads
checksums.txtand the bundle from the published release and verifies them with the exact certificate identity, so a broken signature fails the run visibly.
tag=vX.Y.Z
gh release download "$tag" --repo GrayCodeAI/reflect \
--pattern checksums.txt --pattern checksums.txt.sigstore.json \
--pattern "reflect_${tag#v}_linux_amd64.tar.gz"
cosign verify-blob --bundle checksums.txt.sigstore.json \
--certificate-identity "https://github.com/GrayCodeAI/reflect/.github/workflows/release.yml@refs/tags/$tag" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
sha256sum --ignore-missing -c checksums.txtinstall.sh performs the same checks: it always verifies the SHA-256, verifies
the bundle when cosign (v2.4 or later; v3 recommended) is installed, says so
plainly when it is not, and refuses to install without cosign when
REFLECT_REQUIRE_COSIGN=1.
| Channel | Status |
|---|---|
| GitHub release archives | Pending a Reflect release with matching archives. |
install.sh |
Pending a Reflect release; refuses known hawk-era tags. |
go install github.com/GrayCodeAI/reflect/cmd/reflect@latest |
Use an explicit Reflect tag or @main; historical higher tags affect @latest. |
GitHub Action (.github/actions/reflect) |
Requires Reflect release archives; installs through install.sh with cosign required. |
systemd unit (packaging/systemd/reflect-daemon.service) |
Template for running reflect daemon; see the troubleshooting guide. |
| Homebrew | Not available. Needs a GrayCodeAI/homebrew-tap repository, a fine-grained token with write access to it stored as a secret, and a homebrew_casks stanza in .goreleaser.yml. |
| npm | Not available. The scaffolding in npm/ is marked private; see npm/README.md. |
| Nix | Not available. The old flake could not evaluate and was removed; a new one needs a computed vendorHash, a committed flake.lock and nix flake check in CI. |
Add a channel to the README only after it has worked end to end for a real release.
- Tags
v0.1.0,v0.1.1andv0.2.0were published under the previous module pathgithub.com/GrayCodeAI/hawk; their assets arehawk_*archives containing ahawkbinary. None of them is an ancestor ofmain, andv0.1.1has no GitHub release. The Go proxy keeps serving them, so the currentgo.modretracts[v0.1.0, v0.2.0]. - Because no earlier tag is reachable from
main, the release notes come only from theCHANGELOG.mdsection and the "Full changelog" compare link is omitted. - The historical higher-numbered hawk releases must not be confused with the
reset Reflect series. Publishing a lower version does not automatically make
it the Go proxy's
@latest. Use explicit tags; do not rewrite existing tags.