flux-controller-minor-releases
$
npx mdskill add fluxcd/agent-skills/flux-controller-minor-releasesRuns the upstream Flux controller minor release procedure for seven controllers.
- Automates creating release branches, changelogs, tags, and backport labels for controller minor releases.
- Depends on git, GitHub CLI, and the Flux controller repositories.
- Follows a strict step-by-step procedure based on the upstream Flux release guide.
- Executes git commands sequentially and reports progress, allowing user oversight.
SKILL.md
.github/skills/flux-controller-minor-releasesView on GitHub ↗
---
name: flux-controller-minor-releases
description: >
Run the upstream Flux controller minor release procedure for helm-controller,
image-automation-controller, image-reflector-controller, kustomize-controller,
notification-controller, source-controller, and source-watcher. Use when
cutting a new controller minor release (vX.Y.0): creating the release series
branch, drafting the minor changelog, tagging, merging the release branch back
to main, and adding the backport label.
license: Apache-2.0
---
# Flux Controller Minor Releases
Use this skill for upstream Flux controller **minor** releases (`vX.Y.0`) only —
the release that opens a new `release/vX.Y.x` series. Do not use it for `flux2`,
`pkg`, or other non-controller repos.
Supported controllers:
- `helm-controller`
- `image-automation-controller`
- `image-reflector-controller`
- `kustomize-controller`
- `notification-controller`
- `source-controller`
- `source-watcher`
## Important rules
- **Strictly follow the documented git commands, one step at a time.** Each step
below has a reason. Do not invent substitutions, batch unrelated commands into
one shell invocation, or insert extra verification commands between documented
steps unless asked. Adapt only the version numbers.
- **Never block the conversation on long-running operations.** CI checks, the
tag-triggered release workflow, and approval waits must be watched in the
background (`run_in_background`) so the user can keep steering and you get
notified on completion. A foreground watch blocks the session.
- **Always quote PR/issue links as full URLs** (e.g.
`https://github.com/fluxcd/source-controller/pull/2082`), never the
`<owner>/<repo>#<number>` shorthand — full URLs are clickable from the user's
terminal.
- **Start background watches on every PR immediately after opening it.** Kick off
`gh pr checks <num> -R fluxcd/<repo> --watch` in the background as soon as the
PR is created, and the same for tag-triggered release workflows
(`gh run watch <id> -R fluxcd/<repo>`).
- **Also start a background approval watch per PR.** `gh pr checks --watch` only
covers CI, not maintainer approval. Poll the review state in the background:
```
while :; do
state=$(gh pr view <num> -R fluxcd/<repo> --json mergeStateStatus,reviewDecision --jq '.reviewDecision+" "+.mergeStateStatus')
case "$state" in "APPROVED CLEAN") echo "$state"; break;; esac
sleep 30
done
```
- Every git commit must use `-s` (sign-off). Never include Co-Authored-By lines,
your own name, or any AI attribution in commit messages, PR titles, or PR
descriptions. This applies to the skill-update PR too.
- Always wait for CI to go green before merging any PR.
- You cannot approve your own PRs. If a PR was opened by the git user driving the
session, ask a maintainer to approve it (or confirm it is already approved)
before merging.
- **Merge release PRs yourself** as soon as CI is green **and** a maintainer has
approved — act immediately so the next step (tag push, merge to main, label PR)
unblocks. Applies only to PRs opened with the user's account this session.
- **Review feedback on the release PR is applied by amending**, not by adding new
commits. The release PR must stay at exactly two commits
(`Add changelog entry for vX.Y.0` and `Release vX.Y.0`). Use
`git reset --soft HEAD~2` + re-commit, or an interactive rebase, then
`git push --force-with-lease`.
- **After applying a review fix, reply `Fixed, thanks!` on the thread and resolve
it.** Reply via
`gh api repos/<owner>/<repo>/pulls/<n>/comments/<cid>/replies -f body='Fixed, thanks!'`
and resolve via the GraphQL `resolveReviewThread` mutation.
- Tags must be annotated and signed (`git tag -s -m ...`). Never create release
tags through the GitHub API — that produces lightweight tags which break
`git tag -v` verification.
- **PR titles and bodies.** Release-prep and label PRs use the commit subject as
the title. The release→main PR uses the GitHub-default humanized branch name
`Release/vX.Y.x`. Every PR body is a single line pointing at the flux2 minor
release tracking issue: `Part of: https://github.com/fluxcd/flux2/issues/NNNN`.
- **Do not declare the release done until every step below has run**, including
the final backport label PR (step 11). Tagging and merging to main are not the
last step. Walk the numbered steps and confirm each one ran before reporting
completion.
## Preconditions
- Read the upstream procedure at `website/content/en/flux/releases/procedure.md`,
section `Controllers: minor releases`
(https://fluxcd.io/flux/releases/procedure/#controllers-minor-releases).
- Identify the flux2 minor-release tracking issue so PR bodies can reference it
(`Part of: https://github.com/fluxcd/flux2/issues/NNNN`).
- Use `date` to get the release date for the changelog entry.
- `git fetch --all --tags --prune` before reasoning about branches, tags, or
merged PRs. Do not trust stale local `origin/*` refs.
- Treat `git` and `gh` commands as confirmation points if the user wants that.
## Release Flow
For the controller being released (target version `vX.Y.0`):
1. Create the release series branch from `main` and push it.
- `git switch -c release/vX.Y.x main`
- `git push origin release/vX.Y.x`
2. Create the release preparation branch from the series branch.
- `git switch -c release-vX.Y.0 release/vX.Y.x`
3. Draft the new `CHANGELOG.md` entry (see "How To Build The Changelog Entry").
Then commit it.
- `git add CHANGELOG.md`
- `git commit -s -m "Add changelog entry for vX.Y.0"`
4. Apply the release version bump exactly as documented.
- Update the controller self-API version in the root `go.mod` to `vX.Y.0`.
Inspect the actual `go.mod`; do not assume the self-API path form
(`source-watcher` uses `github.com/fluxcd/source-watcher/api/v2`).
- Update `config/manager/kustomization.yaml` `newTag` to `vX.Y.0`.
- `git add go.mod config/manager/kustomization.yaml`
- `git commit -s -m "Release vX.Y.0"`
5. Push the release preparation branch.
- `git push origin release-vX.Y.0`
6. Open and merge the release PR into the release series branch.
- Base: `release/vX.Y.x` Head: `release-vX.Y.0`
- Title `Release vX.Y.0`, body `Part of: <tracking issue URL>`.
- Merge when CI is green and a maintainer has approved.
7. Refresh the release series branch after the merge.
- `git switch release/vX.Y.x`
- `git pull origin release/vX.Y.x`
- Confirm both version refs are `vX.Y.0` on the merged commit before tagging.
8. Create and push signed tags from the updated release series branch. Push the
`api/` tag first — the release tag depends on it.
- `git tag -s -m "api/vX.Y.0" api/vX.Y.0`
- `git push origin api/vX.Y.0`
- `git tag -s -m "vX.Y.0" vX.Y.0`
- `git push origin vX.Y.0`
9. Verify the release workflow triggered by the `vX.Y.0` tag. Watch it in the
background until it concludes successfully (images published + signed, SBOM,
SLSA provenance, GitHub release created).
10. Merge the release series branch into `main` via PR. This merges the whole
branch (changelog + version bump), not a cherry-pick.
- Base: `main` Head: `release/vX.Y.x`
- Leave the title at the GitHub default — the humanized branch name
`Release/vX.Y.x`. Body `Part of: <tracking issue URL>`.
- Merge when CI is green and approved.
11. **Last:** open the backport label PR against `main`. Do this only **after**
step 10 has merged.
- `git switch main`
- `git pull origin main`
- `git switch -c label-X.Y main`
- Append to `.github/labels.yaml`, after the previous `backport:` entry:
```yaml
- name: backport:release/vX.Y.x
description: To be backported to release/vX.Y.x
color: '#ffd700'
```
- `git add .github/labels.yaml`
- `git commit -s -m "Add backport:release/vX.Y.x label"`
- `git push origin label-X.Y`
- Open PR (base `main`, title `Add backport:release/vX.Y.x label`, body
`Part of: <tracking issue URL>`) and merge when green.
- **Why last:** the label branch is cut from `main`. If you open it before
step 10 merges, that merge moves `main` forward and the label PR must then
be rebased onto the new `main` and force-pushed
(`git rebase main` + `git push --force-with-lease`). Opening it last avoids
the rebase entirely.
## How To Build The Changelog Entry
A minor changelog entry summarizes what is **new in vX.Y.0 relative to the whole
vX.(Y-1) line** — not every commit since the previous minor. The hard part is
selecting exactly the right PRs.
### Selecting the PRs (do this rigorously — do not infer from commit messages)
1. Establish the candidate set: PRs merged to `main` since the previous minor
(`v(X).(Y-1).0`).
- From merge commits:
`git log --merges --grep="Merge pull request" v(X).(Y-1).0..release/vX.Y.x`
and extract the `#NNNN`.
- Cross-check against `gh` so squash/rebase merges are not missed:
`gh pr list --base main --state merged --limit 200 --json number,mergedAt,title`
filtered to merges after the previous minor's release timestamp.
2. **Verify every candidate PR via `gh pr view <n> -R fluxcd/<repo> --json number,baseRefName,state,mergedAt,title`.**
Keep only PRs that are `MERGED` and have `baseRefName == main`. Use the PR
title from GitHub, never the local merge-commit subject (these drift; e.g. a
commit may say one thing while the PR title says another).
3. **Exclude PRs already shipped in a patch release of the previous minor**
(anything `> v(X).(Y-1).0` and `< vX.Y.0`). Read the `## (X).(Y-1).Z` patch
sections already in `CHANGELOG.md` and drop any candidate whose change shipped
there. Note the patch changelogs cite the **cherry-pick** PR numbers
(against `release/v(X).(Y-1).x`), which differ from the original `main` PR
numbers — match by change, not by number.
4. **Exclude release mechanics PRs**: changelog cherry-pick/"Add changelog entry"
PRs, the previous release's `Release/v(X).(Y-1).x` merge-back, and label PRs.
5. Collapse routine dependency bumps (fluxcd/pkg, CI actions, k8s/Go bumps whose
content already shipped in a patch) into a single `Various dependency updates`
bullet listing each PR link. Keep genuinely user-facing items as their own
bullets. See "Dependency update PRs" below before settling for a generic line.
### Writing the entry
Write the new section at the top of `CHANGELOG.md`, matching the existing minor
entries in that repo:
- `## X.Y.0`
- `**Release date:** YYYY-MM-DD` (from `date`)
- A one–two sentence intro naming the headline theme.
- Optional `⚠️` upgrade warnings (API removals, required `flux migrate`, etc.).
When warning about a deprecated/beta API removal, link the upgrade instruction
to the canonical flux2 upgrade-procedure discussion
(https://github.com/fluxcd/flux2/discussions/5572), not to a one-off
`flux migrate` PR — the discussion is the maintained guide covering both the
Flux CLI and Flux Operator migration paths. Older changelog entries may still
point at a migrate PR; do not copy that, use the discussion link.
- Per-API subsections (`### GitRepository`, `### OCIRepository`,
`### HelmChart`, `### Bucket`, …) describing notable features in prose.
- An optional `### General updates` subsection for k8s/Go/dependency posture.
- `Fixes:` and `Improvements:` bullet lists, each bullet a short title plus one
or more `[#NNNN](https://github.com/fluxcd/<repo>/pull/NNNN)` links.
Surface borderline items (repo-internal docs, a dep bump whose content already
shipped in a patch) to the user rather than guessing whether to headline them.
### Dependency update PRs
Do not reduce a dependency bump to a generic line without checking its substance.
- Read the PR description, follow referenced upstream PRs (e.g.
`Includes: fluxcd/pkg#NNNN`), and look at the `go.mod` diff.
- Call out security fixes with their CVE/GHSA and an advisory link plus a short
impact parenthetical. Use matching wording across controllers that pull the
same bump.
- Only mention a dependency change relevant to what that controller actually
does — a bumped module often ships capabilities the controller never exercises.
## Critical Checks
- Always `git fetch --all --tags --prune` before comparing
`v(X).(Y-1).0..release/vX.Y.x` or reasoning about merged PRs.
- The release series branch (step 1) is cut from `main`; the prep branch (step 2)
is cut from `release/vX.Y.x`.
- Pull the release series branch again after merging the release PR and before
tagging. Tag from the series-branch merge commit, not from the prep branch.
- Push `api/vX.Y.0` before `vX.Y.0`.
- The release→main PR merges the whole branch; do **not** cherry-pick for a minor.
- Open the backport label PR last (after step 10 merges) to avoid a rebase.
- Inspect the actual root `go.mod` for the self-API path; do not assume its form.
- Do not silently special-case a controller. If a documented step seems not to
apply, inspect the file and confirm before proceeding.
## Bumping the API in dependent controllers
After a controller minor ships, its API module often needs bumping in the
controllers that depend on it (e.g. `image-reflector-controller/api` in
image-automation-controller, or `source-controller/api` in helm-controller,
kustomize-controller, image-automation-controller, and source-watcher). This is a
separate follow-up PR per dependent repo, not part of the 11 release steps above.
- Branch from the dependent repo's `main` (which may already carry an earlier
bump from the same release round), then
`go get github.com/fluxcd/<controller>/[email protected]` followed by `go mod tidy`.
- The bump is usually **`go.mod` + `go.sum` only**. Mirror an existing sibling PR
from the same round for the exact title/body/commit shape.
- **But check whether the dependent repo pins the dependency's published release
manifests** in `config/default/kustomization.yaml` (remote
`…/releases/download/vX.Y.Z/<controller>.crds.yaml` and `.deployment.yaml`
URLs). If it does, bump those URLs to the new version too. Some repos pin them
(source-controller is pinned by source-watcher, helm-controller, and
kustomize-controller) and some do not (image-automation-controller does not pin
image-reflector-controller). Do not assume — `grep` the repo.
- If the released minor removed APIs, confirm the dependent still builds: it must
not import a removed package version. Run `go build ./...` and `go vet ./...`.
## Updating this skill
- Improvements should land as a single-commit PR on a dedicated branch. When
accumulating more changes during a release session, amend and force-push rather
than adding new commits.
- Keep the skill-update PR open during the session and merge it **last**, after
the release is fully done. Do not keep a CI watch open on it throughout — check
CI right before merging.
- Do not leak session-specific state, downstream/enterprise distribution details,
or AI attribution into the skill file.
## Useful Local Queries
- Existing release branches: `git branch -r --list 'origin/release/v*.x' | sort -V`
- Latest tags: `git tag -l 'v*' | sort -V | tail`
- Candidate PR merges since the previous minor:
`git log --merges --grep="Merge pull request" v(X).(Y-1).0..release/vX.Y.x`
- Full merged-PR cross-check:
`gh pr list --base main --state merged --limit 200 --json number,mergedAt,title`
- Verify a single PR for the changelog:
`gh pr view <n> -R fluxcd/<repo> --json number,title,url,baseRefName,state,mergedAt`