github-release is a reusable release branch, tag, and GitHub Release orchestrator for projects that need predictable release automation without writing large workflow files.
This repository is a public distribution repository. The source code is maintained in the private verzly/toolchain monorepo and this repository contains only the public surface that users need: README.md, action.yml, LICENSE, and GitHub Release assets.
The public repository intentionally does not contain src/, Cargo.toml, build workflows, or release configuration. That separation keeps the user-facing repository small while allowing all tools to share the same release infrastructure in verzly/toolchain.
GitHub releases usually involve the same fragile sequence in every project: create a release branch, update version files, build assets, merge the branch, tag the final commit, generate release notes, upload assets, and clean up temporary branches. When that logic lives directly in YAML, each repository slowly develops its own edge cases.
github-release moves that lifecycle into a typed executable. The workflow stays short, while the dangerous parts such as branch deletion, tag naming, version file updates, and release publishing are handled by one maintained tool.
It was created for the Verzly toolchain model where source code can live in verzly/toolchain, while public distribution repositories receive only release assets and public documentation.
The tool has two release modes.
For a normal source repository, prepare creates a temporary release branch and updates configured version files. After the project-specific build succeeds, finalize merges the release branch into the target branch, creates the tag, optionally creates the GitHub Release, and removes the temporary branch. If the build fails, abort deletes the temporary release branch.
For a distribution repository, publish can create a GitHub Release directly from an already-prepared source tag. This is useful when the source repository and public release repository are different repositories. In that model, release notes can still point to the source repository, so pull request references stay connected to the real code review history.
Use github-release when you want to:
- keep release workflow YAML short and readable;
- standardize release branch names, tag names, release names, and cleanup behavior across repositories;
- update version files before the build runs;
- publish release assets after a successful build;
- generate public release notes from a source repository tag;
- release several public distribution repositories from one private or internal source monorepo.
Run the executable directly from a workflow:
- uses: verzly/github-release@v1
with:
args: plan --version 1.2.3 --config crates/my-tool/github-release.tomlInstall it once and call it from later steps:
- uses: verzly/github-release@v1
with:
install-only: "true"
- run: github-release prepare --version 1.2.3 --config crates/my-tool/github-release.tomlThe composite action detects the runner operating system and CPU architecture, maps that host to a Rust-style target name, downloads the matching executable from this repository's GitHub Releases with gh release download, verifies a .sha256 file when one is present, copies the executable into a temporary bin directory, and adds that directory to PATH.
The action does not build from source. It does not clone verzly/toolchain. It only consumes the release assets published here.
When the action is used through a moving ref such as @latest, @next, @v1, or @v1.2, the installer resolves that ref to the concrete vX.Y.Z or preview release tag pointing at the same commit before downloading assets. This lets workflows use moving action refs while executable assets remain attached to immutable release tags.
| Input | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
github-token |
No | "" |
Any GitHub token readable by gh; empty uses ${{ github.token }} |
Used only to download release assets. Public repositories normally work with the default token. Pass a custom token when downloading from a private fork or restricted environment. |
version |
No | "" |
Empty, latest, next, v1, v1.2, 1.2.3, v1.2.3, or any published release tag |
Selects the release asset to download. Empty uses the action ref when it is a release selector, otherwise the latest release. Moving refs resolve to the concrete vX.Y.Z release tag that has the executable asset. |
install-only |
No | "false" |
String "true" or "false" |
When "true", the action only installs the executable and adds it to PATH. When "false", it installs and immediately runs the executable with args. |
args |
No | --help |
Any valid CLI argument string for the executable | Passed to the installed executable when install-only is not "true". Quote values carefully because this string is evaluated by the shell. |
working-directory |
No | . |
Relative or absolute path | Directory where the executable runs when install-only is not "true". |
| Output | Value | Purpose |
|---|---|---|
bin-path |
Absolute path to the installed executable | Use this when a later step should invoke the exact binary path instead of relying on PATH. |
host-target |
Rust-style host target such as x86_64-unknown-linux-gnu |
Shows which release asset was selected for the current runner. |
github-release --help
github-release init --config github-release.toml
github-release plan --version 1.2.3 --config github-release.toml
github-release prepare --version 1.2.3 --config github-release.toml
github-release finalize --version 1.2.3 --config github-release.toml --assets dist
github-release publish --version 1.2.3 --config github-release.toml --assets dist
github-release floating-tags --config github-release.toml --all
github-release abort --version 1.2.3 --config github-release.tomlTop-level automatic options include --help and --version.
Creates a starter github-release.toml.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-c, --config |
No | github-release.toml |
File path | Where the starter config should be written. |
-f, --force |
No | false |
Boolean flag | Overwrite an existing config file. |
Prints the calculated release plan without changing files, branches, tags, or GitHub Releases.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-v, --version |
Yes | none | SemVer such as 1.2.3, 1.2.3-rc.1, 2.0.0-beta.1 |
Version used to render branch names, tag names, release names, and configured file updates. |
-c, --config |
No | github-release.toml |
File path | Config file to read. |
--target-branch |
No | Config value | Branch name | Temporary override for the branch that receives the release merge. |
--release-branch |
No | Generated from config and version | Branch name | Temporary override for the release branch name. |
Creates the release branch and applies configured version file changes before the project build starts.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-v, --version |
Yes | none | SemVer / prerelease version | Release version. Use the same value later in finalize or abort. |
-c, --config |
No | github-release.toml |
File path | Config file to read. |
--target-branch |
No | Config value | Branch name | Override the target branch. |
--release-branch |
No | Generated | Branch name | Override the release branch. |
--dry-run |
No | false |
Boolean flag | Print planned Git and file operations without executing them. |
--force-branch |
No | false |
Boolean flag | Allow recreating an existing local release branch. Remote branch checks still protect against accidental collisions. |
--commit-message |
No | Config template | String | Override the version update commit message. Template values such as {tag} are normally supplied by config. |
Merges the release branch into the target branch, creates the source tag, optionally publishes a GitHub Release, and cleans up the release branch.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-v, --version |
Yes | none | Same version passed to prepare |
Resolves the release branch, tag, and release name. |
-c, --config |
No | github-release.toml |
File path | Config file to read. |
--target-branch |
No | Config value | Branch name | Override the target branch. |
--release-branch |
No | Generated | Branch name | Override the release branch. |
--assets |
No | none | Directory path | Directory whose files should be uploaded as release assets when GitHub Release publishing is enabled. Nested files are collected recursively. |
--prerelease |
No | auto |
auto, true, false |
Controls the GitHub Release prerelease flag. auto marks SemVer prerelease versions as prereleases. |
--dry-run |
No | false |
Boolean flag | Print Git/GitHub commands without executing them. |
--keep-branch |
No | false |
Boolean flag | Keep the release branch after success instead of deleting it. |
--skip-github-release |
No | false |
Boolean flag | Merge and tag only. Use this for source monorepo tags followed by a separate public distribution release. |
--notes |
No | Config value | String | Use this text as the GitHub Release body instead of generated notes. Cannot be combined with --notes-file. |
--notes-file |
No | none | File path | Read the GitHub Release body from a file instead of generated notes. Cannot be combined with --notes. |
--update-floating-tags |
No | false |
Boolean flag | Update stable major/minor floating tags such as v1.2 and v1 after publishing a GitHub Release. Config can enable this without the flag. |
--update-latest-tag |
No | false |
Boolean flag | Update the configured latest tag after publishing. |
--update-next-tag |
No | false |
Boolean flag | Update the configured next tag after publishing. |
Creates a GitHub Release without preparing or merging a branch. This is the distribution-repository command.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-v, --version |
Yes | none | SemVer / prerelease version | Version to publish. Public distribution repositories normally publish v{version}. |
-c, --config |
No | github-release.toml |
File path | Distribution config to read. |
--assets |
No | none | Directory path | Directory whose files should be uploaded. |
--prerelease |
No | auto |
auto, true, false |
Controls the prerelease flag. |
--dry-run |
No | false |
Boolean flag | Print the GitHub command without creating the release. |
--notes |
No | Config value | String | Use this text as the GitHub Release body instead of generated notes. Cannot be combined with --notes-file. |
--notes-file |
No | none | File path | Read the GitHub Release body from a file instead of generated notes. Cannot be combined with --notes. |
--update-floating-tags |
No | false |
Boolean flag | Update stable major/minor floating tags such as v1.2 and v1 after publishing. Config can enable this without the flag. |
--update-latest-tag |
No | false |
Boolean flag | Update the configured latest tag after publishing. |
--update-next-tag |
No | false |
Boolean flag | Update the configured next tag after publishing. |
Creates or repairs moving tags for already-published releases. With tag_prefix = "v", publishing or analyzing v1.2.3 updates v1.2 and v1. When enabled, latest points to the highest stable vX.Y.Z release, and next points to the highest preview release. If no preview exists, next points to the same commit as latest.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-c, --config |
No | github-release.toml |
File path | Config file to read. |
-v, --version |
No | none | SemVer version | Build the full release tag from config and update enabled moving tags. Use exactly one of --version, --tag, or --all. |
--tag |
No | none | Full SemVer tag such as v1.2.3 or v1.3.0-rc.1 |
Analyze one existing full release tag and update enabled moving tags. |
--all |
No | false |
Boolean flag | Scan all matching SemVer tags, find the highest release for each enabled moving tag, and update them. |
--repository |
No | Config value | owner/repo |
Override github.target_repository. |
--force |
No | false |
Boolean flag | Run and enable all moving tag families even when they are disabled in config. |
--dry-run |
No | false |
Boolean flag | Print planned ref updates without writing tags. |
Deletes a temporary release branch after a failed build.
| Argument | Required | Default | Accepted values | Purpose |
|---|---|---|---|---|
-v, --version |
No | none | Version string | Used to resolve the default release branch. Required unless --release-branch is provided. |
-c, --config |
No | github-release.toml |
File path | Config file to read. |
--release-branch |
No | Generated from version | Branch name | Explicit branch to delete. |
--allow-any-branch |
No | false |
Boolean flag | Disable the configured release branch prefix safety check. Use only when you know exactly what will be deleted. |
--dry-run |
No | false |
Boolean flag | Print deletion commands without executing them. |
A github-release.toml file has three main areas.
[release]
target_branch = "master"
branch_prefix = "release/"
tag_prefix = "v"
tag_suffix = ""
name_prefix = ""
name_suffix = ""
commit_message = "chore(release): prepare {tag}"
merge_message = "chore(release): merge {tag}"
cleanup = true
latest = true
floating_tags = false
latest_tag = false
next_tag = false
[github]
target_repository = "verzly/my-tool"
source_repository = "verzly/toolchain"
source_tag_prefix = "my-tool-v"
source_tag_suffix = ""
generate_notes = true
notes_body = ""
[[files]]
path = "Cargo.toml"
kind = "toml"
key = "package.version"
value = "{version}"
optional = false| Field | Accepted values | Purpose |
|---|---|---|
release.target_branch |
Branch name | Branch that receives the final merge. Defaults are usually master or main. |
release.branch_prefix |
Branch prefix string | Prefix for generated release branches, for example release/. Used as a safety boundary by abort. |
release.tag_prefix / release.tag_suffix |
String | Added around the version to create the tag. Source monorepo tags may use cargo-release-v; public repositories usually use v. |
release.name_prefix / release.name_suffix |
String | Added around the version to create the GitHub Release title. |
release.commit_message |
String template | Commit message for version file updates. |
release.merge_message |
String template | Merge commit message used by finalize. |
release.cleanup |
Boolean | Deletes the release branch after success unless --keep-branch is used. |
release.latest |
Boolean | Controls whether the GitHub Release should be marked as latest. |
release.floating_tags |
Boolean | Enables stable moving major/minor tags such as v1.2 and v1 for public releases. Defaults to false and is ignored for prereleases. |
release.latest_tag |
Boolean | Enables the configured latest tag and points it at the highest stable SemVer release. Defaults to false. |
release.next_tag |
Boolean | Enables the configured next tag and points it at the highest preview SemVer release, or falls back to latest when no preview exists. Defaults to false. |
release.latest_tag_name / release.next_tag_name |
String | Tag names used for the stable and preview channels. Defaults are latest and next. |
github.target_repository |
owner/repo or empty |
Repository where the GitHub Release is created. Empty means the current repository context. |
github.source_repository |
owner/repo or empty |
Repository used for generated release notes. Useful when distribution repositories are source-free. |
github.source_tag_prefix / github.source_tag_suffix |
String | Source tag naming when release notes should be generated from a different repository. |
github.generate_notes |
Boolean | Use GitHub-generated notes when no custom body is provided. Set this to false to create a release without a description unless github.notes_body, --notes, or --notes-file is supplied. |
github.notes_body |
String template | Optional custom GitHub Release body. When non-empty, it takes precedence over generated notes. Supported placeholders are {version}, {tag}, {release_name}, {target_repository}, {source_repository}, {source_tag}, {previous_source_tag}, and {source_compare_url}. |
files |
Array | Version files to update during prepare. Use an empty array for source-free distribution repositories. |
files[].kind |
toml, json, text |
File update strategy. |
files[].key |
Key path or search text | TOML/JSON key path or text target depending on kind. |
files[].value |
String template | New value to write. |
files[].optional |
Boolean | When true, missing files are skipped instead of failing the release. |
Use this flow when the repository contains the actual source code and version files.
github-release prepare --version 1.4.0 --config crates/my-tool/github-release.toml
cargo test --workspace
github-release finalize --version 1.4.0 --config crates/my-tool/github-release.toml --skip-github-releaseprepare creates the release branch and updates configured version files before the build starts. finalize merges the release branch and creates the source tag only after the build and tests succeed.
Use this flow when a public repository only receives generated files and binary assets.
github-release publish --version 1.4.0 --config crates/my-tool/github-release.toml --assets dist/my-toolpublish does not create source branches or edit source files. It creates a GitHub Release from an existing release context and uploads assets.
When release.floating_tags = true, a stable publish updates v1.4 and v1 so GitHub Action users can pin to a major or minor line. When release.latest_tag = true, the latest tag points to the highest stable release. When release.next_tag = true, the next tag points to the highest preview release such as v1.5.0-rc.1, or to the same commit as latest when no preview exists.
Backfill missing floating tags after older releases already exist:
github-release floating-tags --config crates/my-tool/github-release.toml --allUse a custom release body when the public repository should not show generated notes:
github-release publish \
--version 1.4.0 \
--config crates/my-tool/github-release.toml \
--assets dist/my-tool \
--notes "This version was developed in \`verzly/toolchain\`.
Source changes for this package can be reviewed from \`{previous_source_tag}\` to \`{source_tag}\`:
{source_compare_url}"The same body can be stored in github.notes_body. In that case, publish renders the placeholders at release time and uses the result as the GitHub Release description.
github-release abort --version 1.4.0 --config crates/my-tool/source-github-release.tomlabort deletes only the configured release branch by default. Use --allow-any-branch only for manual recovery when you have verified the branch name.
If prepare fails because the release branch already exists, inspect the branch before using --force-branch. If release notes cannot be generated from a private source repository, the tool writes fallback notes instead of silently publishing misleading content. If asset upload fails, verify that gh is authenticated and that the --assets directory contains files rather than empty directories.
Release assets are named by tool, version, and host target. Typical examples:
github-release-v1.2.3-x86_64-unknown-linux-gnu
github-release-v1.2.3-aarch64-unknown-linux-gnu
github-release-v1.2.3-x86_64-apple-darwin
github-release-v1.2.3-aarch64-apple-darwin
github-release-v1.2.3-x86_64-pc-windows-msvc.exe
Checksum files use the same name with .sha256 appended. The action verifies them when the runner has sha256sum or shasum.
github-release shells out to git and gh. CI jobs must check out the repository with enough history and must provide a token that can push branches, push tags, and create releases. For distribution repositories, the token also needs access to the public target repository.
Contribution guidelines live in the verzly/toolchain CONTRIBUTING.md. Source changes are made in verzly/toolchain; this repository is the public distribution surface.
This project is licensed under the AGPL-3.0-only license.