Prepare a release whose tag contains its own changelog, then publish it through
exactly one established release path when the user has authorized publication.
-
Resolve the authorization stage and preflight.
-
State whether this run is Prepare or Prepare + Publish from the user's
request. Do not silently cross from Prepare into Publish.
-
Confirm git and gh are installed and authenticated.
-
Detect the changelog mechanism: git-cliff with a cliff.toml (default), the project's configured release/changelog tool, or a hand-maintained changelog. Confirm the relevant tool is available and print its version.
-
Resolve the base branch from the remote default (do not hardcode main):
BASE_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
-
Ensure a clean working tree and an up-to-date base. List remote tags before
fetching any needed release tag explicitly; do not force-update unrelated
moving tags such as nightly:
git fetch origin "$BASE_BRANCH"
git switch "$BASE_BRANCH" && git pull --ff-only origin "$BASE_BRANCH"
git ls-remote --tags origin
-
Inspect .github/workflows/, release documentation, and package-publishing
configuration. Record the apparent release trigger and owners as a lead;
the publishing gate re-checks them from the merged base before acting.
-
Confirm there is something to release. If there are no releasable commits since the last tag, stop and say so. With git-cliff: git cliff --unreleased is empty. Otherwise: git log <last-tag>..HEAD has nothing release-worthy.
-
Determine the version.
- If the user supplied a version (a
/create-release argument or in the request), use it. Validate that it is well-formed and ahead of the last tag.
- Otherwise, recommend and confirm. Compute a recommended version from the conventional commits since the last tag, then present it together with those commits and ask the user to confirm or choose another. Proceed only once the user has decided — do not auto-pick.
- With git-cliff (default):
VERSION=$(git cliff --bumped-version) for the recommendation; list the commits with git cliff --unreleased (or git log <last-tag>..HEAD).
- Otherwise: derive the recommendation from the project's release tool or the conventional-commit bump rules by hand.
-
Create the release branch off the fresh base, per git-workflow naming:
git switch -c "release/$VERSION"
-
Generate the changelog for that version into the project's changelog file. The intent is to render the section for the unreleased commits under the new version.
- With git-cliff (default):
git cliff --tag "$VERSION" -o CHANGELOG.md regenerates the whole file idempotently; for a large existing file, prepend only the new section with git cliff --tag "$VERSION" --unreleased --prepend CHANGELOG.md.
- Otherwise: produce the same section with the project's changelog tool, or write it by hand following the repo's changelog convention (e.g. Keep a Changelog) from the commits since the last tag.
-
Bump the version wherever it is declared, using the project's own tooling so derived files stay consistent — e.g. cargo set-version "$VERSION" (Rust), npm version "$VERSION" --no-git-tag-version (Node/TypeScript), the pyproject.toml bumper (Python), the gem's version.rb (Ruby), or the manifest field the project's language uses. If the project derives its version from the git tag (e.g. Go modules, setuptools-scm), there is no manifest to bump — the tag is the version, and the release commit carries only the changelog.
-
Validate the prepared release. Run the repository's declared checks that
cover the changelog, version declarations, generated files, build, and tests.
If a required check cannot run, stop or surface the limitation; do not open a
release PR described as ready.
-
Commit the release with the type the generator skips:
git commit -m "chore(release): $VERSION"
Let the pre-commit hooks run (markdownlint on the changelog, etc.); do not skip them.
-
Open the release PR via /create-pr. Title chore(release): $VERSION; body = the new changelog section (with git-cliff: git cliff --unreleased --tag "$VERSION" --strip all; otherwise the section you just wrote). Include the validation performed. It opens as a draft — mark it ready once the diff looks right. If this run is Prepare only, report the PR and stop here.
-
Wait for the release PR to be merged, then verify it. This step belongs to Publish. Do not create or push a tag while the PR is open. The PR must be squash-merged, placing the changelog and version bump on the base branch as one commit.
- Confirm the merge by polling
gh pr view <pr> --json state,mergedAt,mergeCommit until state is MERGED, or by pausing until the user confirms they have merged it.
- The merge is normally left to review/CI and performed by the user; squash-merge it yourself here only if the user has authorized the agent to merge.
-
Run the publication ownership gate. Refresh the merged base, then read the
actual workflow YAML and release documentation again. Search workflow
triggers and steps, not filenames alone, including tag pushes,
workflow_dispatch, release events, gh release, release actions, registry
publishing, and signing or artifact jobs. Check recent workflow runs or
releases when the intended owner is still unclear.
Before acting, state one of these evidence-backed routes:
- Workflow owns tag and release. Use only its documented trigger or wait
for the merge-triggered run. Do not create a tag or call
gh release create.
- Agent owns tag; workflow owns release. Tag and push the verified merge
commit once, then monitor the workflow. Do not call
gh release create.
- Workflow owns tag; agent owns release. Trigger or wait for the workflow,
verify its tag, then create the GitHub release once from that tag.
- Agent owns tag and release. Only when no workflow owns those actions,
tag and push the verified merge commit, then create the GitHub release.
If ownership is ambiguous, multiple paths could publish, or the workflow and
documentation disagree, stop and ask. Do not test the ambiguity by publishing.
-
Execute exactly the selected route. When the agent owns the tag, tag the
squash-merge commit rather than assuming it is HEAD, then push it once:
git switch "$BASE_BRANCH"
git pull --ff-only origin "$BASE_BRANCH"
git tag -a "$VERSION" -m "$VERSION" <merge-commit-sha>
git push origin "$VERSION"
When the agent also owns GitHub release publication, create it once from the
tag using the changelog notes. When a workflow owns publication, monitor its
run to completion instead; never add a manual fallback while it is pending or
failed without first diagnosing the failure and getting approval to change
publishers.
-
Verify and report. Confirm that the published tag points at the intended
merge commit and contains the changelog, and that the single selected
publisher completed. Report the version, tag SHA, release PR URL, GitHub
release URL, workflow run URL when applicable, validation, and artifacts.