Note
cargo-changeset is under active development and is used to manage its own releases. The core workflow is stable, but some features may still evolve. Community testing is very welcome — if you run into any issues, please open an issue.
Releasing Cargo packages — especially in a workspace with multiple crates — requires coordinating version bumps, writing changelog entries, updating dependency declarations, committing, and tagging. Doing this by hand is tedious and error-prone.
cargo-changeset solves this by introducing changeset files: small Markdown files that declare which crates are affected, what kind of version bump each needs, and a human-readable summary of the change. Contributors create these files alongside their code changes. At release time, the tool aggregates all pending changesets and automatically bumps versions in Cargo.toml, updates internal dependency versions, generates Keep a Changelog entries, and creates git commits and tags.
It works with single-crate projects and multi-crate workspaces alike, supports pre-release versions (alpha, beta, rc), and is fully usable in CI environments without interactive prompts.
The release cycle has three phases:
- Capture intent — When making changes, contributors create a changeset file declaring which crates are affected, what bump type each needs, and a summary of the change.
- Verify coverage — In CI,
cargo changeset verifyensures every modified crate has at least one changeset before a pull request can merge. - Release —
cargo changeset releaseconsumes all pending changesets, computes new versions, updatesCargo.tomlfiles, generates changelog entries, and creates a git commit with tags.
A changeset file is a Markdown file with YAML front matter, stored in .changeset/changesets/:
---
category: added
changeset-project: minor
---
Add config option to customize the changelog message template for dependency updatesEach file lists one or more crates with a bump type (major, minor, patch, or none) and an optional Keep a Changelog category (added, changed, deprecated, removed, fixed, security). The body is a free-form description that becomes the changelog entry. The none type allows documenting changes without incrementing the version — by default these are promoted to patch bumps on release, but this behavior is configurable.
| Feature | Details |
|---|---|
| Workspace & single-crate support | Virtual workspaces, workspaces with a root package, and standalone crates. Handles inherited versions (version.workspace = true). |
| Semantic versioning | major, minor, patch, and none bump types with configurable 0.x version semantics. |
| Pre-release versions | Built-in alpha, beta, rc tags and custom identifiers. Automatic counter increment (e.g., alpha.1 → alpha.2). Graduation to stable by releasing without a pre-release flag. |
| 0.x → 1.0.0 graduation | Promote pre-1.0 crates to stable via --graduate or a persistent graduation queue. |
| Dependency-aware releases | Automatically bumps workspace crates that depend on a released crate and generates changelog entries for the update. |
| Keep a Changelog | Generates Keep a Changelog 1.1.0 entries grouped by category. Supports root or per-package changelogs with optional comparison links for GitHub, GitLab, Bitbucket, Gitea, Codeberg, and SourceHut. |
| Git integration | Creates commits and annotated tags automatically. Supports version-only (v1.0.0) and crate-prefixed (crate@v1.0.0) tag formats. |
| CI-ready | Supports GitHub Actions, GitLab CI, CircleCI, Travis, Jenkins, Buildkite, and Azure DevOps. Ships as Docker images and pre-built binaries. Provides GitHub Actions for verify and release. |
Instructions for AI Coding Agents 🤖
If your project uses cargo-changeset, paste the following into your agent configuration file (e.g., CLAUDE.md, AGENTS.md, .cursorrules, or equivalent) so that your coding agent creates changesets automatically as part of its workflow.
## Changeset Policy
This project uses `cargo-changeset` to manage versioning and changelogs.
Every code change that affects a published crate MUST include a changeset.
For detailed usage beyond what is covered here, run
`cargo changeset <command> --help`.
### When to add a changeset
Add a changeset whenever your changes affect the behavior, API, or
dependencies of one or more crates in this workspace. Do NOT add a changeset
for changes that are invisible to users of the crate, such as CI
configuration, documentation-only edits, or test-only refactors.
### How to add a changeset
Run the following command (no interactive prompts):
```bash
cargo changeset add \
--package-bump <crate-name>:<major|minor|patch> \
-m "<description>"
```
For changes affecting multiple crates, repeat `--package-bump` for each:
```bash
cargo changeset add \
--package-bump crate-a:minor \
--package-bump crate-b:patch \
-m "<description>"
```
### Choosing the bump type
- `major` — breaking changes to the public API
- `minor` — new functionality that is backwards compatible
- `patch` — bug fixes and backwards-compatible corrections
### Writing the description
The changeset description appears in the CHANGELOG and is read by users of
the crate, not its developers. Write it from the perspective of someone who
depends on the crate and wants to know what changed and how it affects them.
Keep it to a single sentence when possible.
Good: "Add `--timeout` flag to control request deadline"
Good: "Fix panic when parsing empty configuration files"
Bad: "Refactored the timeout module and added a CLI flag"
Bad: "Fixed bug in config.rs line 42"
### Verifying coverage
After adding a changeset, verify that all affected crates are covered:
```bash
cargo changeset verify --base main
```
Exit code 0 means all changed crates have coverage.cargo install cargo-changesetRequires Rust 1.85.0 or later.
For CI environments, pre-built Docker images are available for linux/amd64 and linux/arm64:
| Registry | Image |
|---|---|
| GHCR | ghcr.io/lukidoescode/cargo-changeset |
| Docker Hub | m3t4lukas/cargo-changeset |
Tags: latest or a specific version (e.g., 0.1.2). See CI/CD Integration for usage examples.
Pre-compiled binaries are available on GitHub Releases for the following platforms:
| Platform | Target |
|---|---|
| Linux x86_64 | x86_64-unknown-linux-musl |
| Linux ARM64 | aarch64-unknown-linux-musl |
| macOS x86_64 | x86_64-apple-darwin |
| macOS ARM64 (Apple Silicon) | aarch64-apple-darwin |
| Windows x86_64 | x86_64-pc-windows-msvc |
Download the archive for your platform, extract the cargo-changeset binary, and place it somewhere on your PATH.
Tip
Every command supports --help for detailed usage information (e.g., cargo changeset add --help).
cargo-changeset automatically detects whether it is running in an interactive terminal. When it is, commands that need user input will prompt for it. When it is not — because stdin is not a TTY, a CI environment variable is detected, or CARGO_CHANGESET_NO_TTY is set — interactive prompts are disabled entirely and all required information must be provided via flags.
Commands are also partially interactive: any information you pass as a flag is accepted as-is, and prompts are only shown for the remaining missing pieces. For example, cargo changeset add --package my-crate in a terminal will accept the package selection and still prompt you for the bump type, category, and description.
Run init in the root of your Cargo project or workspace:
cargo changeset initWith no additional flags, this prompts you through configuring tag format, changelog location, version behavior, and other options step by step. If you pass specific flags (e.g., --tag-format crate-prefixed), only the remaining uncovered settings are prompted. Use --defaults to skip all prompts and accept the built-in defaults.
The command creates a .changeset/ directory and writes the chosen configuration to Cargo.toml under [workspace.metadata.changeset] (or [package.metadata.changeset] for single-crate projects).
After making changes to your code, record your intent to release:
cargo changeset addWith no flags, this prompts you through selecting packages, bump types, a changelog category, and a description. You can supply any combination of flags to skip the corresponding prompts — only missing information is asked for interactively.
To skip all prompts, provide everything on the command line:
cargo changeset add --package my-crate --bump minor --category added -m "Support custom templates"For workspaces with multiple affected crates, use --package-bump to set bump types per crate:
cargo changeset add \
--package-bump my-crate:minor \
--package-bump my-other-crate:patch \
-m "New feature in my-crate, fix in my-other-crate"cargo changeset statusThis shows all pending changesets and the projected version bumps for each crate.
When you are ready to release:
cargo changeset releaseThis consumes all pending changesets, bumps versions in Cargo.toml, updates internal dependency versions, writes changelog entries, and creates a git commit with tags.
Use --dry-run to preview what would happen without modifying any files:
cargo changeset release --dry-runAll commands accept the global -C <PATH> flag to set the project root directory. Run any command with --help for full usage details.
Initialize the .changeset/ directory and write configuration to Cargo.toml.
| Flag | Description |
|---|---|
--defaults |
Accept all defaults without prompts |
--no-interactive |
Disable prompts; use only CLI-provided values |
--tag-format <FORMAT> |
version-only or crate-prefixed |
--changelog <LOCATION> |
root or per-package |
--base-branch <BRANCH> |
Default base branch for comparisons (default: main) |
--zero-version-behavior <B> |
effective-minor or auto-promote-on-major |
--none-bump-behavior <B> |
promote-to-patch, allow, or disallow |
See --help for additional flags covering git, changelog templates, and file filtering options.
Create a new changeset file.
| Flag | Description |
|---|---|
-p, --package <NAME> |
Package(s) to include (repeatable) |
-b, --bump <TYPE> |
Bump type for all selected packages (major, minor, patch, none) |
--package-bump <NAME:TYPE> |
Per-package bump type (repeatable) |
-c, --category <CAT> |
Change category (default: changed) |
-m, --message <TEXT> |
Description; use - to read from stdin |
--editor |
Open $EDITOR for the description |
--exclude-dependents |
Do not compute transitive dependents |
Check that all changed crates have changeset coverage.
| Flag | Description |
|---|---|
--base <BRANCH> |
Base branch to compare against (overrides config) |
--head <REF> |
Head ref to compare (default: HEAD) |
-q, --quiet |
Suppress output; exit code only |
--exclude-dependents |
Do not require coverage for transitive dependents |
--ignore-dirty |
Always compare against base branch, ignoring uncommitted changes |
Show pending changesets and projected version bumps. No additional flags.
Consume changesets and execute the release.
| Flag | Description |
|---|---|
--dry-run |
Preview changes without modifying files |
--convert |
Convert inherited versions (version.workspace = true) to explicit |
--no-commit |
Skip git commit; allows dirty working tree |
--no-tags |
Skip creating git tags |
--keep-changesets |
Do not delete changeset files after release |
--prerelease <CRATE:TAG> |
Create pre-release (e.g., my-crate:alpha); repeatable |
-f, --force |
Force release without changesets (pre-release increment only) |
--graduate <CRATE> |
Graduate a 0.x crate to 1.0.0; repeatable |
Manage persistent pre-release configurations stored in .changeset/pre-release.toml.
| Flag | Description |
|---|---|
--add <CRATE:TAG> |
Add a crate to pre-release (repeatable) |
--remove <CRATE> |
Remove a crate from pre-release (repeatable) |
--graduate <CRATE> |
Move a crate to the graduation queue (repeatable) |
-l, --list |
List current pre-release configuration |
Manage the graduation queue for promoting 0.x crates to 1.0.0, stored in .changeset/graduation.toml.
| Flag | Description |
|---|---|
--add <CRATE> |
Add a 0.x crate to the graduation queue (repeatable) |
--remove <CRATE> |
Remove a crate from the queue (repeatable) |
-l, --list |
List crates marked for graduation |
Configuration is stored in Cargo.toml under [workspace.metadata.changeset] for workspaces or [package.metadata.changeset] for single-crate projects. All keys use kebab-case. Running cargo changeset init writes these values for you.
[workspace.metadata.changeset]
commit = true
tags = true
keep-changesets = false
tag-format = "crate-prefixed"
changelog = "root"
comparison-links = "auto"
zero-version-behavior = "effective-minor"
base-branch = "main"
none-bump-behavior = "promote-to-patch"| Key | Type | Default | Description |
|---|---|---|---|
commit |
bool | true |
Create a git commit on release |
tags |
bool | true |
Create git tags on release |
keep-changesets |
bool | false |
Keep changeset files after release |
tag-format |
string | "version-only" |
"version-only" (v1.0.0) or "crate-prefixed" (crate@v1.0.0) |
changelog |
string | "root" |
"root" (single CHANGELOG.md) or "per-package" (one per crate) |
comparison-links |
string | "auto" |
"auto", "enabled", or "disabled" |
zero-version-behavior |
string | "effective-minor" |
"effective-minor" or "auto-promote-on-major" — see Advanced Topics |
base-branch |
string | "main" |
Default base branch for verify comparisons |
none-bump-behavior |
string | "promote-to-patch" |
"promote-to-patch", "allow", or "disallow" — see Advanced Topics |
none-bump-promote-message-template |
string | "Internal architectural changes" |
Changelog message when none bumps are promoted to patch |
commit-title-template |
string | "{new-version}" |
Template for the release commit title |
changes-in-body |
bool | true |
Include version transitions in the commit body |
comparison-links-template |
string | (auto-detected) | Custom URL template with {repository}, {base}, {target} placeholders |
dependency-bump-changelog-template |
string | "Updated dependency `{dependency}` to v{version}" |
Template for auto-generated dependency bump changelog entries |
ignored-files |
array | [] |
Glob patterns for files to ignore in change detection |
Changeset files live in .changeset/changesets/ and use YAML front matter with a Markdown body:
---
category: fixed
my-crate: patch
my-other-crate: minor
---
Fixed authentication flow and added retry logic to the client.The Markdown body after the closing --- becomes the changelog entry. A single changeset can affect multiple crates, each with its own bump type.
Front matter fields:
| Field | Managed | Required | Default | Description |
|---|---|---|---|---|
| crate-name | no | yes (at least one) | — | Maps a crate name to a bump type: major, minor, patch, or none |
category |
no | no | changed |
One of added, changed, deprecated, removed, fixed, security |
graduate |
no | no | false |
Set to true to graduate a 0.x crate to 1.0.0 on the next release — see Advanced Topics |
consumedForPrerelease |
yes | no | — | Written by the tool during pre-release builds. Records the pre-release version (e.g., 1.0.1-alpha.1) this changeset was consumed in. Do not edit manually. |
cargo-changeset automatically detects CI environments by checking for these environment variables (in order): CI, GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, TRAVIS, JENKINS_URL, BUILDKITE, TF_BUILD. When any of them is set, interactive prompts are disabled and all required input must be provided via flags.
Two environment variables allow explicit control, taking priority over the detection above:
| Variable | Priority | Description |
|---|---|---|
CARGO_CHANGESET_NO_TTY |
Highest | Disables interactive mode unconditionally, even if CARGO_CHANGESET_FORCE_TTY is also set |
CARGO_CHANGESET_FORCE_TTY |
Overrides CI detection | Enables interactive mode even when a CI environment variable is detected |
If neither override is set and no CI variable is found, the tool falls back to checking whether stdin is a terminal.
The composite verify action checks changeset coverage on pull requests using the Docker image. No Rust toolchain or compilation is required.
name: Verify Changeset Coverage
on:
pull_request:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: lukidoescode/cargo-changeset/.github/actions/verify@v1
with:
base: ${{ github.event.pull_request.base.ref }}Important
fetch-depth: 0 is required so that the full git history is available for comparing against the base branch.
The action fetches the base branch from origin and runs verify --base "origin/<base>" --quiet inside the Docker container.
Inputs:
| Input | Default | Description |
|---|---|---|
base |
main |
Base branch to compare against |
cargo-changeset-version |
latest |
Docker image tag to use |
The composite release action bumps versions, updates changelogs, and creates git commits and tags. It requires git user identity to be configured beforehand — the action validates this and fails with a clear error if it is missing.
name: Release
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- uses: lukidoescode/cargo-changeset/.github/actions/release@v1
- name: Push changes and tags
run: git push origin HEAD --tagsThe action reads user.name and user.email from git config and passes them into the Docker container as GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, and GIT_COMMITTER_EMAIL. The action itself does not push — add a push step after it.
Inputs:
| Input | Default | Description |
|---|---|---|
dry-run |
false |
Preview without modifying files |
convert |
false |
Convert inherited versions to explicit |
no-commit |
false |
Skip creating a release commit |
no-tags |
false |
Skip creating git tags |
keep-changesets |
false |
Keep changeset files after release |
force |
false |
Force release without changesets |
prerelease |
— | Space-separated pre-release identifiers (e.g., "foo:alpha bar:beta") |
graduate |
— | Space-separated crate names to graduate (e.g., "foo bar") |
cargo-changeset-version |
latest |
Docker image tag to use |
If you prefer to install from source instead of using the Docker-based actions:
- name: Install cargo-changeset
run: cargo install cargo-changeset
- name: Verify changeset coverage
run: cargo changeset verify --base "${{ github.event.pull_request.base.ref }}"With caching to avoid reinstalling on every run:
- name: Cache cargo-changeset
uses: actions/cache@v5
with:
path: ~/.cargo/bin/cargo-changeset
key: cargo-changeset-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install cargo-changeset
run: cargo install cargo-changeset
- name: Verify changeset coverage
run: cargo changeset verify --base "${{ github.event.pull_request.base.ref }}"Use the Docker image to verify changeset coverage on merge requests:
verify-changesets:
image: ghcr.io/lukidoescode/cargo-changeset:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- cargo-changeset verify --base "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --quietUse the Docker image to execute releases:
release:
image: ghcr.io/lukidoescode/cargo-changeset:latest
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
script:
- git config user.name "gitlab-ci[bot]"
- git config user.email "gitlab-ci[bot]@users.noreply.gitlab.com"
- cargo-changeset release
- git push origin "HEAD:$CI_COMMIT_BRANCH" --tagsNote
Inside the Docker image, the binary is called cargo-changeset (single command with a hyphen), not cargo changeset (cargo subcommand). The entrypoint also automatically adds the mounted directory to git's safe.directory list.
To enforce changeset coverage before every commit, add the following script as .git/hooks/pre-commit (or store it as scripts/pre-commit to commit it alongside your code):
#!/usr/bin/env bash
set -euo pipefail
BASE="${CHANGESET_BASE:-main}"
if ! command -v cargo-changeset &>/dev/null; then
echo "error: cargo-changeset is not installed."
echo "Install it with: cargo install cargo-changeset"
exit 1
fi
cargo changeset verify --base "$BASE"Make it executable and install it:
cp scripts/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commitThe --base flag accepts a local branch name for local usage. Override the default base branch with the CHANGESET_BASE environment variable:
CHANGESET_BASE=develop git commit -m "my change"If your project uses the pre-commit framework, add a local hook to .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: cargo-changeset
name: Verify changeset coverage
language: system
entry: cargo changeset verify
pass_filenames: false
always_run: trueThis calls the locally installed cargo-changeset binary. Install or update the hook with:
pre-commit installcargo-changeset supports pre-release versions with built-in tags (alpha, beta, rc) and custom identifiers. Pre-release versions follow the semver format X.Y.Z-tag.N (e.g., 1.0.1-alpha.1).
Starting a pre-release — pass --prerelease to the release command. If no changesets provide an explicit bump, the tool defaults to a patch bump:
cargo changeset release --prerelease my-crate:alpha| Current Version | Bump | Flag | Result |
|---|---|---|---|
1.0.0 |
patch | --prerelease alpha |
1.0.1-alpha.1 |
1.0.0 |
minor | --prerelease alpha |
1.1.0-alpha.1 |
1.0.0 |
major | --prerelease alpha |
2.0.0-alpha.1 |
Incrementing a pre-release — running with the same tag increments the counter:
| Current Version | Flag | Result |
|---|---|---|
1.0.1-alpha.1 |
--prerelease alpha |
1.0.1-alpha.2 |
1.0.1-alpha.5 |
--prerelease alpha |
1.0.1-alpha.6 |
Switching tags — changing the tag resets the counter to 1:
| Current Version | Flag | Result |
|---|---|---|
1.0.1-alpha.3 |
--prerelease beta |
1.0.1-beta.1 |
1.0.1-beta.2 |
--prerelease rc |
1.0.1-rc.1 |
Graduating to stable — release without --prerelease to strip the pre-release suffix:
| Current Version | Flag | Result |
|---|---|---|
1.0.1-rc.5 |
(none) | 1.0.1 |
For persistent pre-release configuration across multiple release cycles, use cargo changeset manage pre-release to store settings in .changeset/pre-release.toml.
The zero-version-behavior configuration controls how semantic version bumps are interpreted for pre-1.0 crates.
effective-minor (default) — treats the minor version as the effective major version, dampening bumps by one level:
| Current | Bump | Result | Reasoning |
|---|---|---|---|
0.1.2 |
major | 0.2.0 |
Major → minor |
0.1.2 |
minor | 0.1.3 |
Minor → patch |
0.1.2 |
patch | 0.1.3 |
Patch stays patch |
auto-promote-on-major — a major bump on a 0.x version immediately promotes to 1.0.0:
| Current | Bump | Result | Reasoning |
|---|---|---|---|
0.1.2 |
major | 1.0.0 |
Promoted to stable |
0.1.2 |
minor | 0.2.0 |
Normal minor bump |
0.1.2 |
patch | 0.1.3 |
Normal patch bump |
To explicitly graduate a 0.x crate to 1.0.0, use the --graduate flag:
cargo changeset release --graduate my-crate| Current | Flag | Result |
|---|---|---|
0.3.2 |
--graduate |
1.0.0 |
0.5.3 |
--graduate --prerelease alpha |
1.0.0-alpha.1 |
Graduation can only be applied to stable 0.x versions — crates that are already at 1.0.0 or higher, or that are currently in a pre-release, cannot be graduated.
For persistent graduation configuration, use cargo changeset manage graduation to queue crates in .changeset/graduation.toml. You can also set graduate: true in a changeset file to trigger graduation on the next release.
In workspaces, when a crate is released, all crates that depend on it (through [dependencies] and [build-dependencies]) are automatically bumped with a patch version increment. This ensures that dependents always reference the latest version of their workspace siblings.
Auto-bumped crates receive a changelog entry under the "Changed" category using the dependency-bump-changelog-template (default: "Updated dependency `{dependency}` to v{version}"). The --exclude-dependents flag on add and verify skips this dependency tracking.
Only workspace-internal dependencies are tracked — external crate dependencies are not affected.
The none bump type allows documenting changes in a changeset without incrementing the version. This is controlled by the none-bump-behavior configuration:
promote-to-patch(default) —nonebumps are silently promoted topatchon release, using thenone-bump-promote-message-templateas the changelog entryallow—nonebumps are kept as-is; the changeset is consumed but no version change occursdisallow— changesets withnonebumps are rejected
Categories map directly to Keep a Changelog sections and appear in the generated changelog in this order:
- Added — new features
- Changed — changes to existing functionality
- Deprecated — features marked for removal
- Removed — removed features
- Fixed — bug fixes
- Security — vulnerability fixes
The default category is changed. Set it with --category on cargo changeset add or via the category field in the changeset front matter.
Contributions are welcome. Please open an issue to report bugs or suggest features.
Licensed under the MIT License.