Release Process
September 9, 2026 · View on GitHub
This document describes how to create a new release of obs-mcp.
Prerequisites
- A GPG key configured for signing git tags (
git config user.signingkey) - Push access to the repository
Throughout this document, <fork-remote> is the git remote for your fork (for example fork) and <upstream-remote> is the remote for rhobs/obs-mcp (for example upstream). Verify with git remote -v.
Steps
Use these steps for a release cut from current main (minor/major, or a patch when main is the tip you want to ship). If you need a patch while main is ahead with unrelated commits, use Patch releases (when main is ahead) instead.
1. Update CHANGELOG.md and VERSION
Ensure main is up to date:
git checkout main
git pull <upstream-remote> main --rebase
Create a branch:
git checkout -b cut-vX.Y.Z
Update CHANGELOG.md: promote or add a versioned section following the Keep a Changelog format (typically move content from [Unreleased] into the new section and leave an empty [Unreleased]):
## [Unreleased]
## [vX.Y.Z] - YYYY-MM-DD
### Added
- New feature description
### Changed
- Change description
### Fixed
- Bug fix description
Update VERSION: set the file to the release SemVer without the v prefix (for example 0.7.1). This file is the version embedded by make build, make build-linux, and make container. It must match the git tag you create in step 2 (v + contents of VERSION).
echo "X.Y.Z" > VERSION
Commit and push to your fork:
git add CHANGELOG.md VERSION
git commit -m "chore: cut vX.Y.Z"
git push <fork-remote> cut-vX.Y.Z
Open a PR from your fork to upstream main and merge.
2. Create and push the tag (GPG-signed)
Pull the merged release commit into main:
git checkout main
git pull <upstream-remote> main --rebase
Verify VERSION matches the release you intend to tag, then run tests:
cat VERSION # e.g. 0.7.1 → tag will be v0.7.1
make test-unit
make lint
Create a GPG-signed annotated tag with the same version:
export VERSION=$(cat VERSION)
export TAG="v${VERSION}"
make tag VERSION=${VERSION}
Verify the tag:
git verify-tag ${TAG}
git log --oneline -5 # confirm the tag points to the expected commit
Push the tag to the upstream remote for the rhobs org (rhobs/obs-mcp), not your fork. Confirm with git remote -v (often named upstream or origin depending on your clone):
git push <upstream-remote> ${TAG}
# e.g. git push upstream ${TAG}
Pushing the tag to upstream triggers the release workflow, which:
- Runs unit tests
- Builds cross-platform binaries (linux/darwin, amd64/arm64) via GoReleaser
- Signs release archives with cosign (keyless)
- Creates a GitHub release with the binaries, checksums, and auto-generated changelog
3. Verify the release
- Check the Actions tab for the workflow run
- Confirm the release appears under Releases with the expected assets:
obs-mcp_<version>_linux_amd64.tar.gzobs-mcp_<version>_linux_arm64.tar.gzobs-mcp_<version>_darwin_amd64.tar.gzobs-mcp_<version>_darwin_arm64.tar.gzchecksums.txt.bundlesignature files for each archive
Patch releases (when main is ahead)
Use this when you need a patch on an already shipped minor line (for example v0.7.2 after v0.7.1) but main has moved on with other commits you do not want in that patch.
The fix should already be (or also be) on main. You ship the patch from a release branch, cherry-picking only the commits required for the fix.
1. Create or update the release branch
Release branches are named release-X.Y (no v prefix), for example release-0.7 for the 0.7.x line.
Fetch upstream and check whether the branch already exists:
git fetch <upstream-remote>
git branch -r | grep "release-X.Y" || true
If the branch does not exist, create it from the last tag on that line and push it to upstream:
# Example: patching the 0.7 line after v0.7.1
export PREV_TAG=v0.7.1
export RELEASE_BRANCH=release-0.7
git checkout -b ${RELEASE_BRANCH} ${PREV_TAG}
git push <upstream-remote> ${RELEASE_BRANCH}
If the branch already exists, check it out and update it:
git checkout ${RELEASE_BRANCH}
git pull <upstream-remote> ${RELEASE_BRANCH}
2. Cherry-pick the fix from main
The fix commit(s) should already be on main. You copy only those commits onto the release branch via a short-lived branch and a PR whose base is release-X.Y, not main.
Example: shipping v0.7.2 from release-0.7, cherry-picking commit abc1234 from main.
# Start from the release branch (already checked out / up to date from step 1)
git checkout -b cherry-pick-v0.7.2
# Find the fix on main, then cherry-pick it
git log --oneline main
git cherry-pick abc1234
# If the commit is a merge commit: git cherry-pick -m 1 <merge-sha>
# Push this branch to your fork
git push <fork-remote> cherry-pick-v0.7.2
Open a pull request:
| Field | Value |
|---|---|
| base (merge into) | rhobs/obs-mcp → release-0.7 |
| compare (your branch) | <your-fork> → cherry-pick-v0.7.2 |
Do not target main for this PR. After it merges into release-X.Y, continue with the cut steps below on that release branch.
3. Cut CHANGELOG.md and VERSION on the release branch
After the cherry-pick PR is merged, update the release branch and open a cut PR (also targeting release-X.Y, not main):
git checkout release-X.Y
git pull <upstream-remote> release-X.Y
git checkout -b cut-vX.Y.Z
- Add a
## [vX.Y.Z] - YYYY-MM-DDsection toCHANGELOG.mddescribing the patch (do not promote unrelated[Unreleased]content frommain). - Set
VERSIONtoX.Y.Z(novprefix).
echo "X.Y.Z" > VERSION
git add CHANGELOG.md VERSION
git commit -m "chore: cut vX.Y.Z"
git push <fork-remote> cut-vX.Y.Z
Open a pull request with base release-X.Y and compare cut-vX.Y.Z, then merge.
If the changelog note for the patch should also appear on main, open a separate PR to main (or rely on the fix PR’s notes under [Unreleased]).
4. Tag from the release branch (GPG-signed)
git checkout release-X.Y
git pull <upstream-remote> release-X.Y
cat VERSION
make test-unit
make lint
export VERSION=$(cat VERSION)
export TAG="v${VERSION}"
make tag VERSION=${VERSION}
git verify-tag ${TAG}
git push <upstream-remote> ${TAG} # rhobs/obs-mcp, not your fork
Then verify the GitHub Release the same way as in Steps → Verify the release.
Notes
- Do not tag the patch from
mainifmaincontains commits beyond the release line. - Keep
release-X.Yaround for further patches on that line; create it only once per minor line (from the previous tag) if missing. - Minor/major releases from current
mainstill follow Steps above (cut PR intomain, then tag).
Manual release (via workflow dispatch)
A release can also be triggered manually from the GitHub Actions UI:
- Go to Actions > release workflow
- Click Run workflow
- Enter the tag (e.g.,
v0.1.0) and run
Pre-releases
Pre-releases follow the same process as stable releases but use the tag format vX.Y.Z-rc.N. No changelog PR is needed at release time — keep the [Unreleased] section updated as changes land in main, and it will be promoted to a versioned section during the stable release.
git checkout main
git pull <upstream-remote> main --rebase
export VERSION=0.1.0-rc.1
export TAG="v${VERSION}"
make tag VERSION=${VERSION}
git push <upstream-remote> ${TAG} # rhobs/obs-mcp, not your fork
Pre-releases are marked as "pre-release" on GitHub and won't be considered the "latest" release. Use them to:
- Test release artifacts before a stable release
- Get feedback from early adopters
- Verify the release process
Verifying release signatures
All release artifacts are signed using cosign with keyless signing (via GitHub OIDC). Signatures and certificates are stored in bundle files for simplified verification.
# Download artifacts
wget https://github.com/rhobs/obs-mcp/releases/download/v<version>/obs-mcp_<version>_<os>_<arch>.tar.gz
wget https://github.com/rhobs/obs-mcp/releases/download/v<version>/obs-mcp_<version>_<os>_<arch>.tar.gz.bundle
# Verify using bundle
cosign verify-blob \
--bundle obs-mcp_<version>_<os>_<arch>.tar.gz.bundle \
--certificate-identity-regexp 'https://github.com/rhobs/obs-mcp' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
obs-mcp_<version>_<os>_<arch>.tar.gz
The bundle file contains both the signature and certificate, making verification simpler compared to the older separate .sig and .pem files.
Versioning guidelines
Follow Semantic Versioning:
- MAJOR (X.0.0): Incompatible API changes
- MINOR (x.Y.0): New functionality, backwards compatible
- PATCH (x.y.Z): Bug fixes, backwards compatible
Examples
v0.1.0- Initial releasev0.2.0- Added new tools or featuresv0.2.1- Bug fixesv1.0.0- First stable releasev1.0.0-rc.1- Release candidate for v1.0.0
Local testing
To test the release process locally without publishing:
goreleaser release --snapshot --clean
Built artifacts will be in the dist/ directory.