Summary
Nothing keeps our Go version current. operator/go.mod and agent/go/go.mod both pin go 1.26.5, ten workflows resolve their toolchain from those files via go-version-file:, and Dependabot structurally cannot bump either line. A Go stdlib CVE fixed in a patch release produces no PR here and no signal at all.
Dependabot is not going to close this gap. Renovate can. NVIDIA/aicr already runs it, self-hosted, and has worked through most of the decisions we would face. Proposing we follow.
The gap, precisely
Dependabot has no Go-toolchain ecosystem. dependabot-core#13520, "Bump Go toolchain directive in go.mod files", has been an open feature request since November 2025.
The one thing it does do is reactive: dependabot-core#9527 was fixed in September 2025 by removing the code that forced the old go directive back, so native go tooling may now raise the directive as a side effect of a dependency bump that requires it. That has a wrinkle of its own: our Go version can move inside a PR titled chore(deps): bump the kubernetes group, silently changing the toolchain for CI, releases, and CodeQL.
For reference on how far behind that leaves us: aicr is on Go 1.26.6, we are on 1.26.5.
What Renovate actually gives us
Worth being precise, because it is not free. Renovate's gomod manager splits the two directives:
We have no toolchain line and a patch-level go 1.26.5 directive, so out of the box Renovate would do nothing for us either. Closing the gap is an explicit choice between two shapes:
- Keep
go.mod as the source of truth and enable the golang depType. Smallest change, preserves the current go-version-file: wiring across all ten workflows.
- Introduce a
.go-version file as the source of truth, bumped by a customManager on the golang-version datasource, with the gomod manager's golang depType explicitly disabled so the directive cannot move out of band. This is what aicr does.
(1) fits this repo better, since we already centralized on go.mod deliberately. (2) is the proven-here option and decouples "the toolchain we build with" from "the minimum language version we claim to support", which is the distinction the Go team actually intends. Worth deciding, not assuming.
What aicr's rollout already answers
NVIDIA/aicr/.github/renovate.json5 plus .github/workflows/renovate.yaml are the reference. Things they hit that we would otherwise rediscover:
- Self-hosted via
renovatebot/github-action on a cron, not the Mend app. No app install, no org approval.
- Dependabot is kept for
github-actions and Renovate's equivalent manager is disabled. GitHub's auto-issued GITHUB_TOKEN cannot push changes to .github/workflows/* no matter what permissions: says. Renovate would need a GitHub App or a PAT with workflows scope. This means the end state is a hybrid, not a clean replacement, and we should plan for that from the start.
statuses: write and security-events: read are required. Without the first, Renovate 403s writing its stability check, misreports it as "Repository has changed during renovation", and aborts the entire run.
minimumReleaseAge as supply-chain defense. 3 days globally, 7 for anything that auto-merges, so a poisoned-then-yanked release cannot reach us.
- Conventional-commit prefixes via
:semanticCommitTypeAll(chore) / :semanticCommitScope(deps), which is what our own commit-linting PR-title check needs, same reason include: "scope" is set in our dependabot.yml today.
- Our existing
kubernetes and golang-x groups port over directly as matchPackageNames rules, including the !k8s.io/klog/v2 exclusion.
Things specific to us
vendor/ must be in ignorePaths, and postUpdateOptions: ["gomodTidy", "gomodUpdateImportPaths"] has to run, or every PR breaks the -mod=vendor build.
- Two Go modules (
/operator, /agent/go) that should stay on the same Go version. Whatever shape we pick has to bump both together or the mismatch shows up as a confusing CI failure.
.github/labels.yml is a closed set and has dependencies but no renovate label. If we want aicr's labeling, that is a PR to labels.yml first. Nobody creates it by hand.
- Our
docker ecosystem entry covers /containers/**. Renovate's dockerfile manager would take that over, and there is no golang base image in those files today, so no version-skew coupling to worry about (dependabot-core#15135 is the trap we happen to avoid).
- Auto-merge is worth considering for gomod patch bumps, but only after we have seen a few weeks of Renovate PRs pass CI unattended.
Proposed rollout
Mirror aicr's soft launch rather than a cutover:
- Add
.github/renovate.json5 and a self-hosted .github/workflows/renovate.yaml, running alongside the existing dependabot.yml
- Start with
dryRun on workflow_dispatch to see what it proposes before it can open anything
- Port the
kubernetes and golang-x groups so PR shape does not change for reviewers
- Turn on Go version updates in whichever shape we pick above, and confirm both modules move together
- Once Renovate has covered a full cycle, strip
dependabot.yml down to the github-actions ecosystem only
- Revisit auto-merge for gomod patch updates
Acceptance criteria
Summary
Nothing keeps our Go version current.
operator/go.modandagent/go/go.modboth pingo 1.26.5, ten workflows resolve their toolchain from those files viago-version-file:, and Dependabot structurally cannot bump either line. A Go stdlib CVE fixed in a patch release produces no PR here and no signal at all.Dependabot is not going to close this gap. Renovate can. NVIDIA/aicr already runs it, self-hosted, and has worked through most of the decisions we would face. Proposing we follow.
The gap, precisely
Dependabot has no Go-toolchain ecosystem. dependabot-core#13520, "Bump Go toolchain directive in go.mod files", has been an open feature request since November 2025.
The one thing it does do is reactive: dependabot-core#9527 was fixed in September 2025 by removing the code that forced the old
godirective back, so nativegotooling may now raise the directive as a side effect of a dependency bump that requires it. That has a wrinkle of its own: our Go version can move inside a PR titledchore(deps): bump the kubernetes group, silently changing the toolchain for CI, releases, and CodeQL.For reference on how far behind that leaves us: aicr is on Go 1.26.6, we are on 1.26.5.
What Renovate actually gives us
Worth being precise, because it is not free. Renovate's
gomodmanager splits the two directives:toolchaindepType: enabled by defaultgolangdepType (thegodirective): disabled by default, because "it is generally recommended for Go projects to not bump this version unless necessary"We have no
toolchainline and a patch-levelgo 1.26.5directive, so out of the box Renovate would do nothing for us either. Closing the gap is an explicit choice between two shapes:go.modas the source of truth and enable thegolangdepType. Smallest change, preserves the currentgo-version-file:wiring across all ten workflows..go-versionfile as the source of truth, bumped by acustomManageron thegolang-versiondatasource, with thegomodmanager'sgolangdepType explicitly disabled so the directive cannot move out of band. This is what aicr does.(1) fits this repo better, since we already centralized on
go.moddeliberately. (2) is the proven-here option and decouples "the toolchain we build with" from "the minimum language version we claim to support", which is the distinction the Go team actually intends. Worth deciding, not assuming.What aicr's rollout already answers
NVIDIA/aicr/.github/renovate.json5plus.github/workflows/renovate.yamlare the reference. Things they hit that we would otherwise rediscover:renovatebot/github-actionon a cron, not the Mend app. No app install, no org approval.github-actionsand Renovate's equivalent manager is disabled. GitHub's auto-issuedGITHUB_TOKENcannot push changes to.github/workflows/*no matter whatpermissions:says. Renovate would need a GitHub App or a PAT withworkflowsscope. This means the end state is a hybrid, not a clean replacement, and we should plan for that from the start.statuses: writeandsecurity-events: readare required. Without the first, Renovate 403s writing its stability check, misreports it as "Repository has changed during renovation", and aborts the entire run.minimumReleaseAgeas supply-chain defense. 3 days globally, 7 for anything that auto-merges, so a poisoned-then-yanked release cannot reach us.:semanticCommitTypeAll(chore)/:semanticCommitScope(deps), which is what our own commit-linting PR-title check needs, same reasoninclude: "scope"is set in ourdependabot.ymltoday.kubernetesandgolang-xgroups port over directly asmatchPackageNamesrules, including the!k8s.io/klog/v2exclusion.Things specific to us
vendor/must be inignorePaths, andpostUpdateOptions: ["gomodTidy", "gomodUpdateImportPaths"]has to run, or every PR breaks the-mod=vendorbuild./operator,/agent/go) that should stay on the same Go version. Whatever shape we pick has to bump both together or the mismatch shows up as a confusing CI failure..github/labels.ymlis a closed set and hasdependenciesbut norenovatelabel. If we want aicr's labeling, that is a PR tolabels.ymlfirst. Nobody creates it by hand.dockerecosystem entry covers/containers/**. Renovate'sdockerfilemanager would take that over, and there is no golang base image in those files today, so no version-skew coupling to worry about (dependabot-core#15135 is the trap we happen to avoid).Proposed rollout
Mirror aicr's soft launch rather than a cutover:
.github/renovate.json5and a self-hosted.github/workflows/renovate.yaml, running alongside the existingdependabot.ymldryRunonworkflow_dispatchto see what it proposes before it can open anythingkubernetesandgolang-xgroups so PR shape does not change for reviewersdependabot.ymldown to thegithub-actionsecosystem onlyAcceptance criteria
go.mod-as-source-of-truth versus.go-versionoperator/go.modandagent/go/go.modwithin a week of upstream releasego mod vendordependabot.ymlretains only thegithub-actionsecosystem, with a comment explaining why it cannot move.github/labels.ymlin a PR, not created on GitHub