microsoft/release-ahp
Cut a coordinated AHP release (spec + all 5 clients) — bumps every manifest, dates the CHANGELOGs, opens the release PR with auto-merge, and walks through pushing the six per-artifact tags. Use when the user says "release AHP X.Y.Z", "publish AHP", "cut a release", "tag the release", or similar. The skill also captures the recovery procedure for tags whose publish workflow didn't fire.
npx skills add https://github.com/microsoft/agent-host-protocol --skill release-ahp
This skill is the operational counterpart to RELEASING.md
and the cross-cutting AGENTS.md. RELEASING.md is the
canonical reference for what to do per artifact; this skill captures the
order of operations and the foot-guns that bite during a multi-artifact
release.
The repo ships six independently-versioned artifacts but they are released
together by convention — pin the same X.Y.Z across all five client
manifests and the spec on every release unless the user explicitly asks
otherwise.
The user wants to release AHP. Typical asks:
If the user asks for a single-client release (e.g. "just bump Rust"),
prefer the .github/prompts/publish-*.prompt.md files instead — this skill
is for coordinated cross-artifact releases.
A release is two commits in one PR, plus **six tags pushed at the first
commit's SHA after merge**:
docs/.changes/*.json fragments intodated ## [X.Y.Z] CHANGELOG sections, bumps every client manifest from
the previous version → X.Y.Z, and regenerates release-metadata.json
for every client. All six tags must point at this commit's SHA.
PROTOCOL_VERSION intypes/version/registry.ts to the next planned version, prepends it to
SUPPORTED_PROTOCOL_VERSIONS, regenerates each
Version.generated.{rs,kt,swift,go}, and adds a
## [X.Y+1.0] — Unreleased placeholder in the root spec CHANGELOG.md.
Before touching anything, confirm with the user:
X.Y.Z) — the version to use when consumingdocs/.changes/*.json into the six changelogs.
X.(Y+1).0).isolation).
Create release/ahp-<version> off main. Then in a single commit:
npm run changelog:release -- --version X.Y.Z --date <today YYYY-MM-DD>
to collapse docs/.changes/*.json into the six changelogs. This creates
or replaces each ## [X.Y.Z] — <today YYYY-MM-DD> section and adds the
Spec version / Implements AHP X.Y.Z. line automatically.
X.Y.Z:clients/rust/Cargo.toml [workspace.package].version and theversion = "X.Y.Z" pins on ahp-types/ahp inside
[workspace.dependencies].
clients/kotlin/gradle.properties VERSION_NAME.clients/typescript/package.json version.clients/swift/VERSION (bare semver, no v prefix, trailing newline).clients/go/VERSION (bare semver, no v prefix, trailing newline).clients/typescript/: npm install --no-audit --no-fund (no-op ifalready up to date but writes the version into package-lock.json).
clients/rust/: cargo update -w (rewrites only the workspacecrates in Cargo.lock — outside crates stay pinned).
npm run generate:metadata.npm test. This runsverify:change-fragments, verify:release-metadata, and
verify:changelog, which together gate the release: malformed fragments,
a metadata drift, or a missing CHANGELOG heading will fail here and not at
tag-push time.
> Do not bump PROTOCOL_VERSION in this commit. The tag-push
> workflows validate that the registry version matches the tag's version,
> so commit 1 must still have PROTOCOL_VERSION = X.Y.Z.
Commit message:
release: AHP X.Y.Z
Record this commit's SHA. Every release tag points at it.
On the same branch, in a second commit:
types/version/registry.ts:PROTOCOL_VERSION = 'X.(Y+1).0'.SUPPORTED_PROTOCOL_VERSIONS = ['X.(Y+1).0', 'X.Y.Z', ...] (newestfirst; keep any older entries the previous list had).
npm run generate at repo root — this regenerates every client'sVersion.generated.* and every release-metadata.json.
CHANGELOG.md, add a## [X.(Y+1).0] — Unreleased placeholder (with a
Spec version: \X.(Y+1).0\ line) below the existing
## [Unreleased] section. Per-client changelogs already keep their
empty ## [Unreleased] section after fragment consumption.
npm test again.Commit message:
chore: bump PROTOCOL_VERSION to X.(Y+1).0 for ongoing development
git push -u origin release/ahp-X.Y.Z
gh pr create --base main --head release/ahp-X.Y.Z \
--title "release: AHP X.Y.Z" \
--body-file <(...)
gh pr merge <num> --auto --merge
Use --merge, not --squash — the tags need commit 1's SHA to
survive the merge. A squash collapses both commits into a new SHA on
main, and the workflows' PROTOCOL_VERSION check would then fail
because post-merge HEAD is on the next-dev version. A merge commit
preserves both original commits as reachable parents.
If the repo's branch protection forces squash, fall back to either:
retained once tagged), or
Always confirm before merging which strategy preserves commit 1's SHA on
main, and resolve any ambiguity with the user.
The PR body should include:
(they appear under [agent-host-protocol pipelines in
vscode-engineering](https://dev.azure.com/vscode/VSCode/_build)), not
GitHub Actions.
git checkout main
git pull --ff-only origin main
git cat-file -t <RELEASE_SHA> # must print "commit"
If it doesn't exist (squash happened despite intent), open a follow-up
discussion with the user before tagging. Do not tag the squashed
commit on main — its registry shows the post-release version, and
every publish workflow will reject the tag.
RELEASE_SHA=<sha from step 1>
git tag spec/v<X.Y.Z> $RELEASE_SHA
git tag rust/v<X.Y.Z> $RELEASE_SHA
git tag kotlin/v<X.Y.Z> $RELEASE_SHA
git tag typescript/v<X.Y.Z> $RELEASE_SHA
git tag v<X.Y.Z> $RELEASE_SHA # Swift — bare per RELEASING.md
git tag clients/go/v<X.Y.Z> $RELEASE_SHA
git push origin \
spec/v<X.Y.Z> rust/v<X.Y.Z> kotlin/v<X.Y.Z> \
typescript/v<X.Y.Z> v<X.Y.Z> clients/go/v<X.Y.Z>
The six tag schemes are deliberate and not interchangeable. Bare
vX.Y.Z is reserved for Swift (SwiftPM only resolves root-level tags);
clients/go/vX.Y.Z is required by the Go module proxy's sub-module
resolution.
For each tag, verify the corresponding workflow run is queued or in
progress:
GH_PAGER=cat gh run list --limit 10 \
--json databaseId,event,headBranch,status,conclusion,workflowName,displayTitle
Expect to see four GH Actions runs (one per tag below) within seconds:
| Tag | Workflow | Publishes to |
| -------------------- | --------------------- | ------------ |
| spec/vX.Y.Z | Publish Spec | GitHub Release (schema assets) |
| rust/vX.Y.Z | Publish Rust Crates | crates.io (ahp-types, ahp, ahp-ws) |
| vX.Y.Z | Publish Swift Package | SwiftPM (tag-resolved) |
| clients/go/vX.Y.Z | Publish Go Module | Go module proxy (tag-resolved) |
The remaining two tags trigger Azure DevOps pipelines that don't appear
in gh run list:
| Tag | ADO Pipeline | Publishes to |
| -------------------- | ------------------------------------ | ------------ |
| kotlin/vX.Y.Z | clients/kotlin/pipeline.yml | Maven Central via ESRP |
| typescript/vX.Y.Z | clients/typescript/pipeline.yml | npm via ESRP |
For Kotlin/TypeScript, link the user to the AHP pipelines in ADO
(vscode-engineering tenant) to confirm the runs started. Both
pipelines can also be triggered manually from the ADO UI as a hotfix
escape hatch — the validation steps inside each pipeline are identical
to the tag-triggered path.
This has happened. Symptom: tag exists on origin but gh run list
shows no corresponding workflow run.
None of the four GH Actions publish workflows currently declare
workflow_dispatch, so manual dispatch is not available. The recovery
is to delete the remote tag and re-push it at the same SHA — this
emits a fresh push event without changing the tagged commit:
git push origin --delete <tag>
git push origin <tag>
Do not include the --deleted tag in the same git push command as
the re-push — push them in separate git push invocations so each tag
emits its own push event. Re-pushing several tags in a single command
sometimes coalesces into a single event and only one workflow fires.
Once recovery is verified, suggest a follow-up PR adding
workflow_dispatch: to the four publish workflows so the next missed
trigger can be re-run from the Actions UI without tag thrash.
git commit — if you seegpg: keydb_search failed: Operation timed out, run
gpgconf --kill all && rm -f ~/.gnupg/public-keys.d/pubring.db.lock
and retry the commit.
PROTOCOL_VERSION in commit 1 — the publish workflowsre-validate the tag against the tagged commit's PROTOCOL_VERSION,
so a release tag pointing at the post-release commit fails the "Verify
tag matches" step. Always keep the bump in commit 2.
[workspace.dependencies] pins — bumping just[workspace.package].version leaves the cross-crate ahp-types/ahp
pins on the old version. verify-release-metadata does not catch this
but cargo publish --dry-run will.
cargo update -w — without it, Cargo.lock keeps theold version for ahp-types/ahp/ahp-ws and the resulting commit is
partially-bumped. CI's per-language drift check catches it.
VERSION — theread*PackageVersion helpers trim, so functionally it's fine, but
the convention is 0.3.0\n.
vX.Y.Z(no prefix). Every other artifact has a prefix. Putting swift/vX.Y.Z
on Swift's tag silently breaks SwiftPM resolution.
Take microsoft/release-ahp 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.
The instructions reference npm.
Without those the skill loads but fails at the first command.