azure/tool-version-upgrade
>- **WORKFLOW SKILL** — Bumps pinned upstream versions in azd for the GitHub CLI, Bicep CLI, and GitHub Actions referenced in `.github/workflows/*.yml`. Fetches the latest release, confirms with the user, opens a tracking issue, updates source and workflow references, and opens a PR. upgrade github actions, audit workflow action versions, upgrade tool version. (use changelog-generation), deploying releases, publishing extensions to registry.
npx skills add https://github.com/Azure/azure-dev --skill tool-version-upgrade
WORKFLOW SKILL — Upgrades a bundled CLI tool version in azd to the latest upstream release.
INVOKES: GitHub MCP tools, gh CLI, git CLI, go build, ask_user.
| Parameter | GitHub CLI | Bicep CLI | GitHub Actions (workflows) |
| ---------------- | ------------------------------------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------- |
| Tool name | GitHub CLI | Bicep CLI | GitHub Actions |
| Tool slug | gh-cli | bicep-cli | gh-actions |
| Upstream repo | cli/cli | Azure/bicep | (per-action; e.g. actions/checkout, actions/setup-node, golangci/golangci-lint-action) |
| Go version file | cli/azd/pkg/tools/github/github.go | cli/azd/pkg/tools/bicep/bicep.go | n/a |
| Version variable | var Version semver.Version = semver.MustParse("{version}") | same | n/a — versions are pinned per uses: line in YAML |
| Files to update | 1 file (see below) | 2 files (see below) | every .github/workflows/*.yml containing an outdated uses: reference (see below) |
Only one file:
cli/azd/pkg/tools/github/github.goVersion variable: var Version semver.Version = semver.MustParse("{new_version}")// example: https://github.com/cli/cli/releases/download/v{version}/gh_{version}_linux_arm64.tar.gz)
Two files (both must be updated together):
cli/azd/pkg/tools/bicep/bicep.goVersion variable: var Version semver.Version = semver.MustParse("{new_version}").github/workflows/lint-bicep.yml sudo curl -o $(which bicep) -L https://github.com/Azure/bicep/releases/download/v{new_version}/bicep-linux-x64
> ⚠️ Important (Bicep only): Both files must be updated together to keep Go code and CI workflow
> in sync. Forgetting the workflow file is a common mistake — always verify both are changed.
N files — every .github/workflows/*.yml containing an outdated uses: reference. Scope is
limited to that directory; do not edit local actions, reusable workflows in this repo, or
devcontainer feature definitions.
For each uses: owner/repo@ref line, classify and act:
| Bucket | Pattern | Action |
| ----------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Major-tag pin | uses: owner/repo@vN (N is an integer) | Bump to latest stable major (@vM). |
| Exact-version pin | uses: owner/[email protected] or @X.Y.Z | Bump to latest stable major tag (@vM). |
| SHA pin | uses: owner/repo@<40-hex-sha> # vN | Resolve the SHA the latest major tag points to, replace both the SHA and the trailing # vN comment so they stay paired. Never demote a SHA pin to a bare tag. |
| Branch ref | uses: owner/repo@main (or any non-version ref) | Skip, list as warning. Branch refs are usually intentional. |
| Local action | uses: ./... | Skip silently. |
| Local reusable workflow | uses: ./.github/workflows/... | Skip silently. |
> ⚠️ Important (GitHub Actions only): one run produces one tracking issue and one PR
> covering every outdated reference, grouped by action. Do not open one PR per action.
> ⚠️ Supply-chain hardening: SHA-pinned references exist on purpose — typically for workflows
> that gate releases or extension approvals (e.g., approval-ext-azure-ai-agents.yml).
If the user's request doesn't specify which tool, ask via ask_user:
> Which tool would you like to upgrade?
Choices:
Use the corresponding column from the Supported Tools table for all subsequent steps.
gh release view --repo {upstream_repo} --json tagName,name,publishedAt,body
The tag format is v{version} (e.g., v2.86.0 or v0.41.2). Strip the v prefix to get the semver.
After stripping the v prefix, validate the version is strict semver (no pre-release suffixes):
echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' || { echo "Unexpected version format: $version"; exit 1; }
If the latest release is a pre-release (e.g., v0.42.0-rc1), stop and warn the user — do not
use pre-release versions. Suggest the user wait for the stable release or specify a version manually.
If gh release view fails, fall back to:
gh api repos/{upstream_repo}/releases/latest --jq '.tag_name'
grep -n 'var Version semver.Version' {go_version_file}
Extract the version string from the semver.MustParse("X.Y.Z") call.
Bicep only — also verify the CI workflow version matches:
grep -n 'bicep/releases/download' .github/workflows/lint-bicep.yml
If the two versions don't match, warn the user before proceeding.
branch, or PR needed:
> ✅ {tool_name} is already at the latest version ({version}). No update needed.
means the latest GitHub release is not yet stable, or the pinned version was set manually:
> ⚠️ The current {tool_name} version ({current_version}) is newer than the latest release
> ({latest_version}). This may indicate a pre-release pin or a release rollback. No action taken.
ask_user:> The current {tool_name} version is {current_version}.
> The latest release is {latest_version} (released {release_date}).
>
> Proceed with upgrade?
Choices:
> ⚠️ MANDATORY — even in autopilot / auto-approve / yolo mode.
> This confirmation MUST use ask_user and MUST NOT be skipped regardless of agent autonomy settings.
> This is a safety gate to prevent false-positive upgrades (e.g., wrong version parsed, pre-release
> tag picked up).
Present a full summary via ask_user:
> Ready to apply {tool_name} upgrade
>
> - Current version: {current_version}
> - Target version: {latest_version}
> - Upstream release: {release_url}
> - Files to modify:
> {files_list_with_paths}
>
> This will:
>
> 1. Create a tracking issue in Azure/azure-dev
> 2. Create a new branch from origin/main
> 3. Apply the version changes
> 4. Open a PR
>
> Confirm to proceed?
Choices:
If the user cancels, stop immediately — do not create the issue, branch, or PR.
Per references/tool-upgrade-workflow.md § Create Clean Branch from origin/main.
git status --porcelain). If dirty, stop and warn:> Your working tree has uncommitted changes. Please commit or stash them before running
> this skill, so the upgrade PR contains only the version bump.
Do NOT proceed with dirty state — do not use git stash automatically.
origin/main: git fetch origin main
git branch -D update/{tool_slug}-{latest_version} 2>/dev/null || true
git checkout -b update/{tool_slug}-{latest_version} origin/main
git --no-pager log --oneline origin/main..HEAD
Must produce no output. If it shows any commits, abort.
GitHub CLI: Edit cli/azd/pkg/tools/github/github.go:
semver.MustParse("{old}") with the new version.Bicep CLI: Edit both files:
cli/azd/pkg/tools/bicep/bicep.go — replace version in semver.MustParse("{old}")..github/workflows/lint-bicep.yml — replace old version in the curl download URL. cd cli/azd && go build ./...
If the build fails, delete the branch, report the error, and stop.
Do NOT create an issue or PR for a broken build.
grep 'MustParse' cli/azd/pkg/tools/bicep/bicep.go | head -1
grep 'bicep/releases/download' .github/workflows/lint-bicep.yml
git add -A):GitHub CLI:
git add cli/azd/pkg/tools/github/github.go
Bicep CLI:
git add cli/azd/pkg/tools/bicep/bicep.go .github/workflows/lint-bicep.yml
git --no-pager diff --cached --stat
Output must show ONLY the expected files. If unexpected files appear, abort.
> Note: The issue is created after the build succeeds to avoid orphan issues when the
> build or staging validation fails.
gh issue create \
--repo Azure/azure-dev \
--title "Update {tool_name} version to {latest_version}" \
--body "{issue_body}"
GitHub CLI issue body:
Update GitHub CLI version from {current_version} to {latest_version}.
Release: https://github.com/cli/cli/releases/tag/v{latest_version}
### Files to update
- [ ] `cli/azd/pkg/tools/github/github.go` — update `Version` variable to `{latest_version}`
Bicep CLI issue body:
Update Bicep CLI version from {current_version} to {latest_version}.
Release: https://github.com/Azure/bicep/releases/tag/v{latest_version}
### Files to update
- [ ] `cli/azd/pkg/tools/bicep/bicep.go` — update `Version` variable to `{latest_version}`
- [ ] `.github/workflows/lint-bicep.yml` — update download URL to `v{latest_version}`
Capture the issue number from the output.
git commit -m "Update {tool_name} to v{latest_version}" \
-m "Fixes #{issue_number}"
git push -u origin update/{tool_slug}-{latest_version}
gh pr create \
--repo Azure/azure-dev \
--title "Update {tool_name} to v{latest_version}" \
--body "{pr_body}" \
--base main
GitHub CLI PR body:
Updates the bundled GitHub CLI version from {current_version} to {latest_version}.
Fixes #{issue_number}
Release: https://github.com/cli/cli/releases/tag/v{latest_version}
## Changes
- Bumped `Version` constant in `cli/azd/pkg/tools/github/github.go`
Bicep CLI PR body:
Updates the bundled Bicep CLI version from {current_version} to {latest_version}.
Fixes #{issue_number}
Release: https://github.com/Azure/bicep/releases/tag/v{latest_version}
## Changes
- Bumped `Version` constant in `cli/azd/pkg/tools/bicep/bicep.go`
- Updated download URL in `.github/workflows/lint-bicep.yml`
Present a summary to the user:
> ✅ Done!
>
> - Issue: #{issue_number} — {issue_url}
> - PR: #{pr_number} — {pr_url}
> - Version: {current_version} → {latest_version}
When the selected tool is GitHub Actions, Steps 2, 3, 4, 6, 7, and 8 differ from the
single-tool flow above. Steps 1 (Identify Tool) and 5 (Final Confirmation Gate) work the
same way. The branch slug is gh-actions-versions (no version suffix, since this run
covers many actions at once).
Scan every workflow file:
git grep -nE '^\s*uses:\s+[^ ]+' -- '.github/workflows/*.yml'
Parse each line into one of the buckets in GitHub Actions (workflows) — Files to Update.
Deduplicate by (owner/repo, current_ref) so each unique reference is fetched only once.
For each unique owner/repo:
gh release view --repo {owner}/{repo} --json tagName,name,publishedAt,isPrerelease
Treat the leading integer of tagName as the latest major (e.g., v6.0.1 → v6).
If the latest release is a pre-release (isPrerelease: true), fall back to:
gh release list --repo {owner}/{repo} --limit 20 --json tagName,isPrerelease \
| jq -r '[.[] | select(.isPrerelease==false)][0].tagName'
If the tag does not match v[0-9]+(\.[0-9]+\.[0-9]+)?, skip this action and add it
to the warnings list — do not guess.
For SHA-pinned references, additionally resolve the commit SHA the major tag currently
points to:
gh api repos/{owner}/{repo}/git/refs/tags/v{major} --jq '.object.sha'
If .object.type == "tag" (annotated tag), follow once:
gh api repos/{owner}/{repo}/git/tags/{sha} --jq '.object.sha'
For each parsed reference, compute the desired new value per the buckets table. If
new == current, drop from the change list.
If the change list is empty after this step, inform the user and stop — no
issue, branch, or PR needed:
> ✅ All GitHub Actions references are already at the latest stable major. No updates needed.
If any reference has a cross-major jump (e.g., v4 → v6 skipping v5), record a
breaking-change advisory for the Step 5 confirmation prompt so the user can
spot-check release notes before approving.
Use the standard gate, but the summary must show:
current → new and the affected file count.Example summary body:
> GitHub Actions upgrade — ready to apply
>
> | Action | Current | New | Files |
> | --------------------- | --------------- | --------------- | ----- |
> | actions/checkout | v4 | v6 | 14 |
> | actions/setup-node | v4 | v6 | 7 |
> | actions/github-script | v7 (SHA-pinned) | v9 (SHA-pinned) | 1 |
>
> Skipped (branch refs): actions/stale@main in stale-issues.yml
>
> ⚠️ Cross-major jumps: actions/checkout v4 → v6 (skips v5).
Use the standard clean-branch flow but with a fixed slug:
git fetch origin main
git branch -D update/gh-actions-versions 2>/dev/null || true
git checkout -b update/gh-actions-versions origin/main
git --no-pager log --oneline origin/main..HEAD # must be empty
Apply edits with these constraints:
uses: lines. Never use a project-wide find-and-replace.# vN comment ina single edit so they stay paired.
After all edits, sanity-check the diff for accidental SHA-pin demotion:
git --no-pager diff -- '.github/workflows/*.yml' | grep -E '^[-+].*uses:'
If a -uses: owner/repo@<sha> # vN line appears paired with a +uses: owner/repo@vM
without an SHA, abort and report.
There is no Go build for workflow-only edits. Validate YAML parses:
for f in .github/workflows/*.yml; do
python -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$f"
done
If any file fails to parse, abort and revert the edits to that file. If every
file fails, abort the whole run.
If actionlint happens to be installed locally, run it as an extra check:
actionlint .github/workflows/*.yml || true
Do NOT install actionlint automatically — the project's own lint workflows will
catch issues on the PR.
Stage explicitly — never git add -A:
git add .github/workflows/*.yml
git --no-pager diff --cached --stat
The diff stat must contain ONLY paths under .github/workflows/. If anything else
is staged, abort.
Issue title: Update GitHub Actions to latest stable versions
Issue body:
Audit and bump GitHub Actions referenced in `.github/workflows/*.yml` to the latest
stable major versions.
### Upgrades
{table_of_action_to_current_to_new_to_file_count}
### Skipped
{list_of_skipped_branch_refs_or_unparseable_tags}
### Files modified
{bullet_list_of_workflow_files}
Commit & push:
git commit -m "Update GitHub Actions to latest stable versions" \
-m "Fixes #{issue_number}"
git push -u origin update/gh-actions-versions
gh pr create \
--repo Azure/azure-dev \
--title "Update GitHub Actions to latest stable versions" \
--body "{pr_body}" \
--base main
PR body:
Bump all GitHub Actions referenced in workflow files to their latest stable major versions.
Fixes #{issue_number}
## Upgrades
{table_of_action_to_current_to_new_to_file_count}
## Notes
- Major-tag pins (`@vN`) preserved.
- SHA pins (`@<sha> # vN`) preserved as SHA pins, with both the SHA and the `# vN`
comment updated together to stay in sync.
- Branch refs (e.g., `@main`) intentionally skipped.
- Cross-major jumps (where applicable) called out below; release notes should be
reviewed before merge.
{breaking_change_callouts_if_any}
> ✅ Done!
>
> - Issue: #{issue_number} — {issue_url}
> - PR: #{pr_number} — {pr_url}
> - Actions bumped: {count}
> - Files modified: {file_count}
major tags by default; SHAs only where the file already SHA-pins.)
setup-go with: go-version: — that's a separateworkflow (validate-go-version).
setup-* with: blocks..github/workflows/ (e.g., devcontainer features).If the user asks for any of the above, tell them this skill does not cover it.
gh release view fails → fall back to gh api repos/{upstream_repo}/releases/latestbicep.go and lint-bicep.yml before upgrade → warn user, ask which to use as baselinegh release view fails for an action → fall back togh release list and pick the newest non-prerelease; if still none, skip the action
and add it to warnings
it from the change list, continue with the rest. If every file fails, abort the run.
→ abort, report the file/line, do not commit
@v3) no longer exists upstream becausethe action was renamed/archived → skip and add to warnings; never silently rewrite to
a different owner/repo
Take azure/tool-version-upgrade from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.