| name | bump-ci-tool-versions |
| description | Bump the pinned CLI tool versions that GitHub Actions workflows download at runtime (helm, kind, skaffold, cosign, golangci-lint, goreleaser, etc.) — the `*_VERSION:` env vars and the `# vX.Y.Z` comments next to SHA-pinned `uses:` actions. Use for "update workflow tool versions" (also invoked as `workflow-tool-versions`), "CI tool refresh", "bump skaffold/kind/cosign", or processing a Dependabot github-actions PR. Generic to any repo with GitHub Actions workflows. |
Bump CI tool versions
Playbook for refreshing the CLI tool versions a repo's GitHub Actions workflows pin.
Optimised for isolating regressions: each tool lands as its own commit, so CI-failure attribution is one git blame.
Project-agnostic.
For this repo's known tool inventory, which workflows pin what, and any deliberately-stale pins (e.g. an old major kept on purpose to test an upgrade path), read its CLAUDE.md and .claude/resources/ — re-discover dynamically (Phase 0) rather than trusting any snapshot.
Scope (priority order)
- Primary —
env:-block *_VERSION: vars the workflow downloads at runtime (Dependabot does NOT touch these).
- Secondary —
# vX.Y.Z comments next to SHA-pinned uses: actions (Dependabot usually bumps these; a manual pass is catch-up when it's behind/paused).
- Tertiary — version matrices (e.g. a
kindversion: of k8s node images), tied to the tool's support window; bump only when the underlying binary supports the newer target.
Out of scope: floating constraints (with: version: "~> v2" — no pin to bump), and tool versions living in Makefile/hack//Dockerfile/release config (those go with the build code, not the runners).
Phase 0 — Baseline
Branch off the default branch with a date-stamped name (avoids a stale merged collision): git checkout <default> && git checkout -b ci/tool-versions-<YYYY-MM-DD>.
Inventory dynamically — don't trust any snapshot:
grep -rnE "^\s*[A-Z_]+_VERSION:" .github/workflows/
grep -rnE "uses:.*@[a-f0-9]{40} # v" .github/workflows/
Resolve each tool to its upstream repo and fetch latest (gh api authenticates automatically):
for repo in <owner1>/<tool1> <owner2>/<tool2>; do
echo "$repo -> $(gh api "repos/$repo/releases/latest" --jq '.tag_name')"
done
Phase 1 — Decide what to bump
Patch: take unconditionally.
Minor: read upstream notes for renamed/dropped config keys (linters/build tools).
Major: don't lump into a sweep — separate branch with its own validation.
Flag deliberate stale pins the repo documents (e.g. an old major kept to test an upgrade path, or a matrix entry intentionally lagging) — leave them unless the user says otherwise.
Phase 2 — Per-tool workflow
$EDITOR .github/workflows/<file>.yaml
ruby -ryaml -e 'ARGV.each { |f| YAML.load_file(f) }; puts "YAML OK"' .github/workflows/<file>.yaml
git add .github/workflows/
git commit -m "Bump <tool> v<old> -> v<new>"
If a var lives in multiple files with the same intent (e.g. "latest stable kind" in two workflows), bump them in one commit.
If intents differ (a tool pinned old in one workflow on purpose), keep separate commits.
When the user asks to bump a SHA-pinned action:
- Resolve the new tag to its commit SHA (the pin is a 40-hex SHA, not the tag):
gh api repos/<owner>/<repo>/commits/<tag> --jq '.sha'.
Update both the @<sha> and the trailing # vX.Y.Z comment.
- One action often appears in several files — replace every occurrence with the identical new
@<sha> # vX.Y.Z and commit together.
Edit with replace_all per file is reliable; a shell perl -pi -e one-liner with #/spaces silently no-ops, so re-grep to confirm.
Phase 3 — Verification
There's no local equivalent for "did this workflow still pass" — CI is the test.
git log --oneline <default>..HEAD to review the per-tool commits.
- Push and let CI run on the PR.
- If one tool's commit breaks CI, revert that single commit without touching the rest — the point of the per-tool structure.
Pitfalls
- Config-schema migrations on minor bumps (golangci-lint, skaffold, etc.): a bump can fail CI with a config-validation error.
Run the tool's own config-verify against the new binary locally (
golangci-lint config verify, skaffold diagnose --profile <p>) and migrate deprecated keys rather than disabling config.
- A version var read by multiple jobs: confirm every consumer still reads the env var (not a hardcoded version) after editing — some tools are installed twice in one workflow.
- Dependabot overlap.
Dependabot groups action SHA bumps but not runtime
*_VERSION: vars, so the primary scope rarely overlaps.
Before declaring a SHA-pinned action "behind", check for an open Dependabot PR: gh pr list --author "app/dependabot" --state open.
If a github-actions group PR already bumps it, don't duplicate — flag it in the summary so the user reconciles (whichever merges first, the other rebases).
Canonical outline
1. branch ci/tool-versions-<YYYY-MM-DD>
2. grep .github/workflows/ for *_VERSION vars + SHA-pinned uses
3. gh api repos/<owner>/<repo>/releases/latest per tool
4. per tool (patch/minor; defer majors): edit, validate YAML, commit; group same-intent files
5. push; let CI verify
6. summarize: bumps, deferred majors, flagged intentional stale pins, Dependabot overlap