Skip to main content

Release Process

llmenv follows semantic versioning and automates release distribution via GitHub Actions.

Overview

Releases are triggered by pushing a version tag (v*) to the main branch only. The release workflow:

  1. Builds binaries for macOS (arm64, x86_64) and Linux (x86_64)
  2. Generates SHA256 checksums for all binaries (automatic)
  3. Publishes to crates.io (requires valid CARGO_REGISTRY_TOKEN secret)
  4. Creates a GitHub Release with pre-built binaries and checksums attached

Changelog

CHANGELOG.md follows Keep a Changelog and Semantic Versioning. There is exactly one rule that matters and it is easy to get wrong:

A version section only exists once that version has been git-tagged. Until then, everything lives under ## [Unreleased].

The Cargo.toml version and the changelog must never run ahead of the tags. If git tag -l shows no vX.Y.Z tag, there is no [X.Y.Z] changelog section and Cargo.toml is not bumped to it. (This repo previously accumulated phantom 1.0.0/1.1.0/1.2.0 sections with no tags behind them — don't recreate that.)

While developing (every change)

Add an entry under ## [Unreleased] in the appropriate category. Do not touch the Cargo.toml version and do not create a new version heading.

Categories (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. This repo also uses Documentation. Reference the issue/PR number in the entry, e.g. (#63).

## [Unreleased]

### Added

- New `llmenv foo` subcommand that does X. (#81)

When cutting a release (and only then)

  1. Decide the version X.Y.Z from the nature of the [Unreleased] entries (breaking → major, feature → minor, fix-only → patch).
  2. Rename ## [Unreleased] to ## [X.Y.Z] - YYYY-MM-DD and add a fresh empty ## [Unreleased] above it.
  3. Bump version in Cargo.toml to X.Y.Z and run cargo build so Cargo.lock updates in the same commit.
  4. Commit (chore: release X.Y.Z), then proceed to Version Tags below to tag and push.

The version bump, changelog rename, and lockfile update land together in the release commit — never piecemeal across unrelated changes.

Version Tags

Create a tag in the format vX.Y.Z and push it to the main branch:

# Version already bumped in Cargo.toml + CHANGELOG.md (see Changelog section)
cargo build --release # Test locally first
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin main vX.Y.Z # Push to main branch

Important: Tags must be pushed from the main branch. Releases triggered from feature branches are blocked by the workflow.

Binary Distribution

GitHub Releases

Pre-built binaries are attached to each release on GitHub:

  • llmenv-linux-x86_64 — Linux x86_64
  • llmenv-macos-x86_64 — macOS Intel (x86_64)
  • llmenv-macos-aarch64 — macOS Apple Silicon (arm64)
  • checksums.txt — SHA256 checksums for all binaries

Verify Binary Integrity

Each release includes a checksums.txt file. Verify downloaded binaries:

sha256sum -c checksums.txt

All binaries are automatically checksummed during the release build.

crates.io

The Rust crate is published to crates.io. Install with:

cargo install llmenv

Prerequisites:

  • A valid CARGO_REGISTRY_TOKEN must be set as a GitHub Actions secret
  • Generate tokens at https://crates.io/me

Homebrew

A Homebrew tap is maintained at phaedrus1992/homebrew-tap.

Install:

brew install phaedrus1992/tap/llmenv

Update:

brew upgrade llmenv

Maintenance

Adding a new platform

To add a new platform (e.g., Windows, aarch64 Linux):

  1. Update .github/workflows/release.yml:

    • Add a new matrix entry under build-binaries
    • Set os, target, asset_name
  2. Test locally:

    rustup target add <target>
    cargo build --release --target <target>
  3. Update Homebrew formula if a new macOS target is added:

    • Modify Formula/llmenv.rb in phaedrus1992/homebrew-tap
    • Add conditional blocks for the new architecture

Rollback

If a release needs to be pulled:

# Mark as pre-release on GitHub (manual UI)
# Unpublish from crates.io (requires crates.io owner access)
cargo yank --vers X.Y.Z
# Remove from Homebrew (PR to phaedrus1992/homebrew-tap)

Secrets Configuration

The release workflow requires:

  • CARGO_REGISTRY_TOKEN — crates.io API token (scoped to publish only)

Set this in the repository settings under Secrets and variables → Actions.

Security notes:

  • The token is passed via environment variable (never command-line arguments)
  • GitHub Actions automatically masks secret values in logs
  • Always use fine-grained tokens with minimal scope (publish-only)

Future Work: SLSA Provenance and Homebrew Automation

The following enhancements are tracked for future releases:

SLSA Build Provenance

  • Integrate slsa-framework/slsa-github-generator for cryptographic proof of build chain
  • Attach SLSA provenance to GitHub releases
  • Enables users to verify binaries were built from claimed source by GitHub Actions

Homebrew Automation

  • Auto-generate and update Formula/llmenv.rb SHA256 hashes after release
  • Reduce manual steps and error potential in the homebrew-tap repo
  • Trigger automation from llmenv release workflow

Troubleshooting

Release workflow doesn't trigger

  • Verify tag was pushed to the main branch
  • Workflow only runs when tag is pushed to main, not feature branches
  • Check GitHub Actions tab for workflow run details

Publish fails with "unauthorized"

  • Verify CARGO_REGISTRY_TOKEN is valid and has publish scope
  • Check token hasn't expired

Binary artifacts missing from release

  • Check the build-binaries job succeeded in Actions tab
  • Verify artifact paths match in create-release
  • Checksums should always be generated automatically

Checksum verification fails

  • Ensure you're on the same system/shell as the release CI
  • Download the binary and checksums.txt from the same release
  • Run sha256sum -c checksums.txt in the download directory