flux-controller-patch-releases — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited flux-controller-patch-releases (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
A bulleted imperative like {match} tells the agent to never reveal, disclose, or mention something to the user. Used adversarially it can instruct the agent to hide its tool calls or lie about what it did — stripping the transparency a user relies on to trust the agent.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill for upstream Flux controller patch releases only. Do not use it for flux2, pkg, or other non-controller repos.
Supported controllers:
helm-controllerimage-automation-controllerimage-reflector-controllerkustomize-controllernotification-controllersource-controllersource-watcherthrough every step you can do locally — branch, changelog commit, version bump commit, push, open PR — before switching to the next. Only move on when you hit something you cannot progress (CI running, PR awaiting review, tag workflow running). The moment something unblocks (PR merged, CI green, workflow finished), come back to it immediately — do not finish the current controller's local work first if an earlier one is ready to advance.
release workflow runs, tag-triggered workflows, and similar waits must be watched in the background so the user can keep steering and so you can pick up any other controller the moment it unblocks. When a background watch completes, report the result and proceed.
https://github.com/fluxcd/helm-controller/pull/1465), never the <owner>/<repo>#<number> shorthand — full URLs are clickable from the user's terminal, the shorthand is not.
off gh pr checks <num> -R fluxcd/<repo> --watch in the background as soon as the PR is created so CI status lands in the conversation the moment it finishes. Do the same for tag-triggered release workflows (gh run watch <id> -R fluxcd/<repo> in the background). Do not wait until "everything is pushed" to start watching — start watching the first PR while you prepare the second. Note that gh pr checks --watch can finish early, reporting success on a set of checks that does not yet include a run it just re-triggered, so restart the watch (and re-read gh pr checks <num>) after any amend or force-push.
gh pr checks --watchonly covers CI; it does not fire on maintainer approval. Poll the review state in the background so you are notified the moment it flips to APPROVED + CLEAN:
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
doneRun this in the background; when it exits, merge the PR and proceed. The session's git user opens these PRs, so they cannot approve them: request a review from a maintainer with gh pr edit <num> -R fluxcd/<repo> --add-reviewer <login>, so the approval watch has a reviewer to fire on.
-s (sign-off). Never include Co-Authored-Bylines, your own name, or any AI attribution in commit messages, PR titles, or PR descriptions. This applies to all PRs, including PRs that update this skill file itself.
the session, ask a maintainer to approve it (or confirm it is already approved) before merging.
PRs) as soon as CI is green and a maintainer has approved. No need to ask the user to click merge — act on it immediately so the next step (tag push, etc.) unblocks. This applies only to PRs opened with the user's account during this session.
new commits. A release PR must stay at exactly two commits (Add changelog entry for vX.Y.Z and Release vX.Y.Z). When the fix belongs in the changelog, amend the changelog commit; when it belongs in the release bump, amend that one. Use git reset --soft HEAD~2 + re-commit, or an interactive rebase, then git push --force-with-lease.
Fixed, thanks! on the thread andresolve it.** Reply via gh api repos/<owner>/<repo>/pulls/<n>/comments/<cid>/replies -f body='Fixed, thanks!' and resolve via the GraphQL resolveReviewThread mutation. Find thread IDs with gh api graphql -f query='{ repository(owner:"<o>",name:"<r>") { pullRequest(number:<n>) { reviewThreads(first:50) { nodes { id isResolved comments(first:1){nodes{databaseId body}} } } } } }'.
merge at the very end of the procedure, so check CI right before merging rather than keeping a watch open throughout the session.
git tag -s -m ...). Never createrelease tags through the GitHub API — that produces lightweight tags which break git tag -v verification.
not invent substitutions or skip steps — each step has a reason.
below has been executed for it*, including the final changelog cherry-pick PR back to `main` (step 11) and that PR being merged. Merging the release PR and tagging is not* the last step, and neither is opening the cherry-pick PR. Before reporting completion, walk through each controller against the numbered steps and confirm each one ran.
every controller has been through all eleven steps and no PR opened during it is still waiting to be merged. Do not stop to report progress and wait for a prompt when nothing is blocking you — reporting is not a step, and "the PRs are open" is not a finish line. The only legitimate pauses are external: CI still running, a PR awaiting a maintainer's approval, a release workflow in flight. When one of those clears, act on it immediately instead of asking whether to continue.
Release vX.Y.Z)as the PR title and an empty body — never the changelog commit subject.
website/content/en/flux/releases/procedure.md,section Controllers: patch releases.
git and gh commands as confirmation points if the user wants that.date to get the release date for changelog entries.local origin/* refs.
Do this first, for every supported controller, before touching any of them. Compare the tip of each controller's latest release series branch against the most recent tag on that series. If the branch is ahead of its tag, it needs a patch release and the next version is the next patch increment of that tag.
for repo in helm-controller image-automation-controller image-reflector-controller \
kustomize-controller notification-controller source-controller source-watcher; do
cd ~/Documents/github.com/fluxcd/$repo
git fetch --all --tags --prune
br=$(git for-each-ref --format='%(refname:lstrip=3)' 'refs/remotes/origin/release/v*.x' | sort -V | tail -1)
series=${br#release/}; series=${series%.x}
tag=$(git tag -l "${series}.*" --sort=-v:refname | grep -v '\-' | head -1)
ahead=$(git rev-list --count "${tag}..origin/${br}")
echo "$repo $br latest=$tag ahead=$ahead next=${tag%.*}.$(( ${tag##*.} + 1 ))"
doneRead the output as follows:
ahead=0 — nothing to release for that controller; skip it.ahead>0 — the controller needs a patch release at the printed next version.vN.M) is an automatic patch candidate.Older supported series are patched only for critical fixes, and picking one is a deliberate decision — never infer it from the loop alone.
ahead>0 because a release PR is already open for it. Checkgh pr list -R fluxcd/<repo> --state open --base release/vX.Y.x before starting a new one, and resume that PR instead of opening a duplicate.
gh api repos/fluxcd/<repo>/git/matching-refs/tags/v --jq '.[].ref' — local tags and refs go stale when a fetch fails, which silently hides or invents unreleased commits.
For each controller identified above as needing a patch release:
git fetch --all --tags --prunegit switch release/vX.Y.xgit pull origin release/vX.Y.xgit switch -c release-vX.Y.Z release/vX.Y.xCHANGELOG.md entry.date.the previous tag.
commit messages.
PRs, and the go.mod diff for notable content (see "Dependency update PRs" below) rather than settling for a generic bump line.
patch release.
git add CHANGELOG.mdgit commit -s -m "Add changelog entry for vX.Y.Z"go.mod.config/manager/kustomization.yaml newTag to vX.Y.Z.git add go.mod config/manager/kustomization.yamlgit commit -s -m "Release vX.Y.Z"git push origin release-vX.Y.Zrelease/vX.Y.xrelease-vX.Y.Zgh pr merge <num> -R fluxcd/<repo> --merge),not a squash: the tagged commit on the release series branch must keep both signed commits and the DCO history.
git switch release/vX.Y.xgit pull origin release/vX.Y.xPush the api/ tag first — the release tag depends on it.
git tag -s -m "api/vX.Y.Z" api/vX.Y.Zgit push origin api/vX.Y.Zgit tag -s -m "vX.Y.Z" vX.Y.Zgit push origin vX.Y.Zapi/ tag triggered the release workflow.release.yaml on the vX.Y.Z tag; wait for it withgh run watch <id> -R fluxcd/<repo> --exit-status, then confirm a non-draft GitHub release was published (gh release view vX.Y.Z).
main.git switch maingit pull origin maingit switch -c pick-changelog-vX.Y.Z maingit cherry-pick -x <Add changelog entry commit>git push origin pick-changelog-vX.Y.Zpick-changelog-vX.Y.Z to maincherry-picks into a single shell invocation. If such a batch is interrupted partway, the branches it already created make a later git switch -c fail, which silently leaves you on main, so the next git cherry-pick lands the changelog commit on local main instead of the pick branch. Before cherry-picking, confirm you are on pick-changelog-vX.Y.Z; if the branch already exists, switch to it (or delete and recreate it) rather than letting switch -c fail.
For a patch release, gather:
origin/release/vX.Y.x since that tagWrite the new section at the top of CHANGELOG.md:
## X.Y.Z**Release date:** YYYY-MM-DDFixes: when there are bug-fix itemsImprovements: for dependency updates, docs, feature gates, or cleanupRules:
Do not reduce a dependency bump to a generic "Update fluxcd/pkg dependencies" line and move on — the user-facing substance is usually hidden inside the bump. Weigh Flux's own repos and third-party dependencies differently:
bump is how a fix we made reaches the controller. Trace every module in the go.mod diff back to its commits, e.g. in a local clone of fluxcd/pkg: git log --oneline --no-merges <mod>/<old>..<mod>/<new> -- <mod>, filtering out Prepare for release and the shared Upgrade k8s to ... commits. Read the PR behind each remaining commit and describe the fix in the controller's own terms. Doing this across all the modules of one release usually leaves a handful of real changes, which is a short enough list to reason about per controller.
not recount an upstream project's release notes, and do not list upstream CVE/GHSA advisories that merely rode along with a bump. Mention an upstream change only when it resolves an issue reported by Flux users, or when the upgrade itself is the point (e.g. syncing the Helm version with the one helm-controller ships) — and then say why it matters to Flux, not what changed upstream.
bring Kubernetes to 1.36.4". Read the version off the go.mod diff for that specific bump instead of assuming one is there: a fluxcd/pkg update may carry a Kubernetes bump, a module fix, both, or neither, and the same release window can move k8s.io/* for one controller and not for another.
as careful about wrongly leaving it out as about wrongly putting it in. A fix can land in a function the controller never calls, in which case it does not belong in that changelog; or in one the controller already calls, in which case the behavior change ships without any code change in the controller and must be in the changelog. grep the controller for the changed symbol and for the type that owns it. Example: runtime/client rejecting file references in kubeconfigs reached kustomize-controller purely through Impersonator, which kustomize-controller uses and the other controllers do not — so it changed how .spec.kubeConfig Secrets are accepted with no kustomize-controller commit behind it.
their changelogs so readers can correlate them.
actually does. A bumped module often ships capabilities the controller never exercises, and listing them implies a capability it does not have. For example, GCP sovereign cloud artifact registry support (shipped via a pkg/auth bump) is worth noting for source-controller and image-reflector-controller, which pull from registries, but not for image-automation-controller, which only talks to Git.
tag..origin/release/....release-vX.Y.Z.before tagging.
go.mod; do not assume the self-API path form.source-watcher uses github.com/fluxcd/source-watcher/api/v2, so it still needs the same self-API release bump pattern.
apply, inspect the file and confirm before proceeding.
main, not the release version bump.git reset --soft HEAD~2, do not git checkout go.mod or config/manager/kustomization.yaml — that restores them from the index and silently reverts the bump, leaving a release PR that only touches the changelog. Rebuild both commits, then check with git diff origin/release/vX.Y.x --stat that CHANGELOG.md, go.mod and config/manager/kustomization.yaml are all still modified.
branch. When accumulating more changes during a release session, amend and force-push rather than adding new commits.
last, after all controller patch releases are done. Session learnings tend to surface throughout the flow; amend them in as they come up.
details, or AI attribution into the skill file.
git branch -r --list 'origin/release/v*.x' | sort -Vgit tag -l 'v*' | sort -V | tailgit log --oneline <prev-tag>..origin/release/vX.Y.xgh pr view <number> -R fluxcd/<repo> --json number,title,url,baseRefName~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.