Release Skill Quirks for Jarvy¶
The cutting-pre-release, validating-pre-release, and cutting-release
skills were authored for a Go project that uses different release plumbing
than Jarvy. This document records every divergence so the skills can be
applied here without confusion.
The canonical Jarvy release process is in
docs/release-testing.md. When this file disagrees
with that file, defer to that file.
Workflow filename¶
- Skills reference
release.yaml. Jarvy's file is.github/workflows/release.yml. - Whenever a skill says
gh run list --workflow=release.yaml, substitute--workflow=release.yml(or use the workflow's display nameBuild and Release).
Tag format and matcher¶
Jarvy's release workflow triggers on:
Both stable and pre-release tags fire the same workflow. The workflow's
prerelease-detection step inspects ${{ github.ref_name }} for a - and
sets prerelease: true on the GitHub release accordingly.
Tag signing — enforced¶
The workflow has a Verify tag is signed step that runs git tag -v and
fails the build if the tag is not signed. Use git tag -s (GPG) or
git -c gpg.format=ssh tag -s (SSH signing) when cutting any tag. Without
a signed tag, no release artifact is produced.
The maintainer's signing key (GPG public key or SSH allowed-signer line) must be:
- Registered on the maintainer's GitHub profile (Settings → SSH and GPG keys for GPG; Settings → SSH and GPG keys → Signing for SSH)
- Configured locally in
~/.gitconfig(user.signingkey,gpg.format,commit.gpgsign,tag.gpgsign)
Skill assumption holds: the workflow rejects unsigned tags, same as the omni-infra-provider-truenas pattern.
Cosign artifact signing — keyless OIDC¶
Jarvy uses Sigstore cosign keyless signing via GitHub Actions OIDC. No long-lived keys are stored as secrets. The workflow mints an ephemeral certificate per release run and signs every binary, package, SBOM, and checksum artifact. Verification:
# Substitute a triple that matches your target — one of:
# aarch64-apple-darwin, x86_64-unknown-linux-musl,
# x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu,
# armv7-unknown-linux-gnueabihf, x86_64-pc-windows-msvc (.zip).
ARTIFACT=jarvy-v0.5.1-x86_64-unknown-linux-musl.tar.gz
cosign verify-blob \
--signature "${ARTIFACT}.sig" \
--certificate "${ARTIFACT}.pem" \
--certificate-identity-regexp "https://github.com/Cliftonz/jarvy/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"$ARTIFACT"
Same pattern as omni-infra-provider-truenas. No skill divergence.
Release notes — awk-extracted from CHANGELOG.md (omni pattern)¶
Jarvy uses the same awk extraction pattern as the
omni-infra-provider-truenas release workflow. The Build release notes
step in .github/workflows/release.yml:
- Runs
awkagainstCHANGELOG.mdlooking for the## [vX.Y.Z](or## [X.Y.Z]) section that matches the tag, taking everything until the next## [header. - Falls back to
git log <prev-tag>..<tag> --pretty='- %s (%h)'when no CHANGELOG entry exists. This fallback is the intended path for pre-release tags — by policy (seeCHANGELOG.md"Policy" section), pre-releases do not get a CHANGELOG entry. - Appends a
**Full Changelog**: <compare-link>line. - Appends Jarvy's standing footer (install commands, signature verify, SBOM info, tag-verify command).
- Writes the result to
RELEASE_NOTES.mdand feeds it tosoftprops/action-gh-releaseviabody_path:.
Implications:
- The
cutting-releaseskill's "update CHANGELOG before tagging" step is load-bearing — if you forget, the awk extraction returns empty and the release falls through togit lognotes, which read like a raw commit list rather than a curated narrative. - The awk pattern is
/^## \[v?VERSION\]/to/^## \[/. CHANGELOG entries must use exactly that header shape (## [vX.Y.Z]or## [vX.Y.Z] — Title); other formats won't match. - The
cutting-pre-releaseskill's "no provisional rc entry" rule applies for the opposite reason than its docs imply on Jarvy: it's not that an RC entry would be ignored, it's that RCs are expected to fall through to the git-log path. Adding an RC CHANGELOG entry would override the fallback and produce inconsistent rc release notes between iterations.
Release auto-publishes — behind a draft-verification gate¶
Updated 2026-07-14 (gate model): the workflow creates a draft, uploads
all assets, then runs a draft-verification gate — re-downloads the
draft's assets via the API and checks checksums, cosign signatures, SBOM
shape, and a binary --version smoke against the tag — and only on green
auto-promotes to published (--draft=false, --latest for stables).
Implications:
- The tag push commits you to the pipeline, but a bad build now self-aborts at the draft: nothing user-facing happens, no withdrawal needed. Recovery from a red gate is delete draft + tag, fix, re-tag.
- Never hand-publish a draft around a red gate (
gh release edit --draft=falseby hand) — the gate is the checkpoint that replaced the manual verify step; bypassing it is how v0.6.1 class mistakes ship. - Post-publish, verify-release.yml re-checks the PUBLIC assets and the rest of the fleet exercises the public install/upgrade paths — those are rollback triggers, not gates.
(History: pre-2026-07-13 the workflow stopped at the draft for a manual publish; 2026-07-13 it auto-published with verification only afterwards — which let the mislabeled v0.6.1 ship, see issue #62; the gate landed 2026-07-14.)
release:published events use the tag's commit, not main HEAD¶
When publish-packages.yml (or any other workflow listening on
release: published) fires, GitHub Actions checks out the workflow YAML
from the commit the release tag points at, not the default branch
HEAD at event time. This breaks the intuitive "fix it on main before
publishing the draft" workflow.
Discovered when adding a prerelease gate to publish-packages.yml:
- Cut
v0.1.0-rc.10at commitbcdff1d. - Added a
!github.event.release.prereleasegate topublish-packages.yml, committed ase1bb4caon main. - Published the draft for
v0.1.0-rc.10. publish-packagesran, butgh run view 25737549987 --json headShashowedheadSha = bcdff1d(the tag's commit) — not main HEAD. The gate didn't exist in that workflow file.cargo publishstarted running before the run was manually cancelled.
Implications:
- Workflow changes intended to affect a specific release must be committed before the release tag is created, and the release tag must descend from that commit.
- Re-running a cancelled
release: publishedworkflow run will replay against the same tag-SHA workflow file — fixes on main do not apply retroactively. To bring a fix to bear, cut a new tag (-rc.(N+1)) whose commit includes the fix. workflow_dispatchtriggers DO use the workflow file from whichever branch/SHA the user dispatches against — so manual reruns can pick up main's fixes if invoked with the appropriate ref.
Mitigation in this repo: the prerelease gate landed in
e1bb4ca and is included in every rc cut from main going forward. The
risk window for v0.1.0-rc.10 itself is closed because the run was
cancelled before cargo publish completed.
Universal install scripts — FIXED (issue #30)¶
dist/scripts/install.sh and .ps1 build:
Historically these 404'd because release.yml only shipped
.dmg / .msi / .exe / .deb / .rpm / .AppImage. As of
issue #30 (landed pre-v0.5.1), release.yml now also produces:
jarvy-v${VER}-aarch64-apple-darwin.tar.gz(macOS arm64)jarvy-v${VER}-x86_64-pc-windows-msvc.zip(Windows)- (Linux tarballs across all three triples were already shipping.)
Install scripts should Just Work starting with the first release
cut after this section landed. The JARVY_CHANNEL piping gotcha
(env var must be right of curl … |, not left) is unrelated to
tarballs and still applies.
Homebrew pipeline — partially unblocked (issue #30)¶
Issue #30 added the .tar.gz / .zip artifacts the Homebrew
formula in Cliftonz/homebrew-tap was authored against. The
tarball naming convention now matches the formula's URL pattern:
jarvy-v${VER}-aarch64-apple-darwin.tar.gzjarvy-v${VER}-x86_64-unknown-linux-gnu.tar.gzjarvy-v${VER}-x86_64-unknown-linux-musl.tar.gzjarvy-v${VER}-aarch64-unknown-linux-gnu.tar.gz
Two remaining blockers before brew install Cliftonz/tap/jarvy
works end-to-end:
HOMEBREW_TAP_DEPLOY_KEYsecret must be configured sopublish-packages.yml::update-homebrew::Push to Homebrew tapcan commit the SHA-substituted formula to the tap repo. Configuration steps indocs/MAINTAINER_RELEASE_GUIDE.md.jarvy.rbin the tap repo must be reset — as of the last audit it still contained literalVERSION_PLACEHOLDER/SHA256_PLACEHOLDER_*strings from initial setup 2026-01-18, because every prior update run silently skipped (secret unset). Any release cut after fix (1) will substitute the placeholders automatically.
Until (1) + (2) land, Homebrew still isn't a working distribution
channel. macOS soak coverage runs through install.sh (now
functional post-#30) and cargo install.
Path 1 is the cleanest long-term — adds Homebrew to real cohort coverage and lets the existing publish-packages workflow finish what it started. Tracked separately; not a v0.1.0 blocker.
Package publish — dispatch wiring + per-channel bootstrap (2026-07-05 audit)¶
Cutting v0.5.0 surfaced three separate breakages in the package-publish path. Two are fixed in-repo; two are one-time external setup that only a maintainer with the relevant account/secret can do.
publish-packages.yml was never triggered (FIXED)¶
publish-packages.yml keys on release: published. That event does
not fire downstream workflows when the release is published by the
release workflow's built-in GITHUB_TOKEN (GitHub's recursion guard —
same root cause as the release-paths / verify-release /
prerelease-soak dispatches documented above). v0.5.0's release was
published by github-actions[bot], so nothing published — crates.io
had no 0.5.0 and jarvy update (cargo path) failed with
could not find jarvy in registry with version =0.5.0.
Fix (release.yml::upload_to_release):
- Added
actions: writeto the job. Without it thegh workflow rundispatch step 403'd (Resource not accessible by integration) andset -eaborted — which had also been silently starving release-paths / verify-release / prerelease-soak of their per-release triggers (they ran on cron only). - Added a stable-only
gh workflow run publish-packages.ymldispatch. Stable-only is load-bearing: publish-packages' crates-io gate readsgithub.event.release.prerelease, which is null on aworkflow_dispatchand therefore treated as "not a prerelease" — a dispatch always publishes. An unguarded dispatch on an rc tag would push the stableCargo.tomlversion to crates.io as latest.
v0.6.0 onward auto-publishes. To publish a release cut before the fix,
dispatch manually: gh workflow run publish-packages.yml -f version=X.Y.Z.
AUR — SSH key must have no passphrase (needs maintainer)¶
Every release the update-aur job fails with:
KSXGitHub/github-actions-deploy-aur has no passphrase input — the
AUR_SSH_PRIVATE_KEY secret must be an unencrypted private key.
Regenerate and re-register:
ssh-keygen -t ed25519 -C "aur@jarvy" -f aur_key -N "" # -N "" = no passphrase
# add aur_key.pub to the AUR account (My Account → SSH keys)
# set repo secret AUR_SSH_PRIVATE_KEY = contents of aur_key, then delete local copies
winget — package must be bootstrapped once (needs maintainer)¶
Every release the update-winget job fails with:
Package Jarvy.Jarvy does not exist in the winget-pkgs repository.
Please add atleast one version of the package before using this action.
winget-releaser only submits updates — it bases new manifests on
an existing version. Jarvy.Jarvy has never been added to
microsoft/winget-pkgs, so the first version is a one-time manual
submission (subject to the public-pr-guard skill — Microsoft
maintainers review the PR):
winget install wingetcreate
wingetcreate new https://github.com/Cliftonz/jarvy/releases/download/v0.5.0/jarvy_0.5.0_x64_en-US.msi
# identifier Jarvy.Jarvy → --submit
After that PR merges, the action handles all future versions. Two
adjacent fixes already applied to publish-packages.yml:
- Action owner renamed
vedantmgoyal2009→vedantmgoyal9; pinned the new owner (redirect can lapse). WINGET_TOKENmust be a classic PAT withpublic_reposcope — winget-releaser (Komac) does not accept fine-grained PATs. The prior comment mis-documented it as fine-grained.
Multi-channel propagation¶
Jarvy ships through eight distribution channels. The skills assume one
GitHub release plus one downstream catalog (TrueNAS apps). Jarvy's channel
propagation is documented in
docs/release-testing.md.
Summary:
| Channel | Auto / Manual |
|---|---|
| GitHub release | Auto (CI) |
| crates.io | Auto (publish-packages.yml::publish-crates-io job) — publishes via Trusted Publishing (OIDC), no stored token. Requires a one-time Trusted Publisher config on crates.io for both jarvy and jarvy-templates (owner Cliftonz, repo jarvy, workflow publish-packages.yml). Once a release proves it, the CRATES_IO_TOKEN secret can be deleted. |
| Homebrew tap | Auto (CI PR to Cliftonz/homebrew-tap) |
.deb, .rpm, .msi, .dmg, .AppImage |
Auto (release.yml asset) |
AUR (jarvy-bin) |
Auto-attempt (publish-packages.yml::update-aur) — blocked until the SSH key is re-keyed without a passphrase (see bootstrap section) |
| winget | Auto-attempt (update-winget) — blocked until Jarvy.Jarvy is bootstrapped with a one-time manual PR; updates auto after |
| Chocolatey | Auto (publish-packages.yml::update-chocolatey) |
Universal install scripts (install.sh, install.ps1) |
No update needed; scripts auto-fetch latest from GitHub API |
The public-pr-guard skill applies for winget submissions
(microsoft/winget-pkgs is a non-user-owned repository).
Asset name patterns¶
The skills' install snippets use generic asset names. Jarvy's actual
patterns, set by the build matrix in release.yml:
| Platform | Asset pattern |
|---|---|
| macOS arm64 | jarvy_<version>_aarch64.dmg |
| macOS x86_64 | jarvy_<version>_x64.dmg |
| Linux x86_64 (musl) | jarvy_<version>_amd64.deb, jarvy_<version>_x86_64.AppImage, jarvy-<version>-1.x86_64.rpm |
| Linux arm64 | jarvy_<version>_arm64.deb, jarvy-<version>-1.aarch64.rpm |
| Linux armv7 | jarvy_<version>_armhf.deb |
| Windows x86_64 | jarvy_<version>_x64_en-US.msi, jarvy_<version>_x64-setup.exe |
| Tarball binaries (install scripts) | jarvy-v<version>-<arch>-<os>.tar.gz (Unix), .zip (Windows) |
Plus per-asset:
- *.sig — Sigstore signature
- *.pem — Sigstore certificate
- *.bundle — Sigstore bundle
- SHA256SUMS.txt — checksums file
- sbom.spdx.json, sbom.cdx.json — SBOMs
Opt-in early-release channel¶
Jarvy has an opt-in beta channel that the skills don't model:
- Install scripts honor
JARVY_CHANNEL=beta(ornightly) and fetch the latest GitHub prerelease via the/releasesAPI instead of/releases/latest. ~/.jarvy/config.toml[update] channel = "beta"makesjarvy updatepull prereleases.JARVY_UPDATE_CHANNEL=betaenv var overrides the config value at runtime.jarvy update --channel betafor one-shot beta installs.
The implementation is src/update/release.rs::matches_channel plus
src/update/config.rs::Channel::matches_version. Beta accepts -rc and
-beta suffixes; Nightly accepts everything; Stable rejects any version
containing -.
For the soak cohort to actually exist, this opt-in path must work — see Cohort Environment in the canonical doc.
What stays the same as the skills¶
These skill behaviors apply to Jarvy unchanged:
- "Tag is the point of no return" — the workflow builds and signs atomically on tag push.
- Pre-release tag identifier convention:
-rc.N(default),-beta.N,-alpha.N. Always start at.1for a new target version. - Per-version explicit authorization for tag pushes (don't reuse a prior session's approval).
cutting-pre-releasestep "no provisional rc entry in CHANGELOG" applies with the same reasoning (RCs see auto-notes; CHANGELOG entry is written once when stable cuts).validating-pre-releaseoverall flow (trigger matrix → matrix → soak → promotion criteria → catalog bump) — the canonical procedure (release-testing.md) is structured the same way, just with Jarvy-specific paths.
What the skills should not do on Jarvy¶
- Do not edit
microsoft/winget-pkgsdirectly. Usewingetcreateand let Microsoft maintainers review the PR. - Do not auto-publish the GitHub draft release until the maintainer has verified artifacts.
- Do not generate a CHANGELOG entry for an rc tag. The auto-notes are the contract for pre-releases.
- Do not assume the awk extraction step from the cutting-release skill applies — it does not.