Releasing from any CI
A marketplace release is three steps the CLI gives you primitives for: build with release gates, produce checksums, publish a tagged release. Every CI system runs the same three commands. The wrappers below differ only in syntax.
The canonical sequence
Section titled “The canonical sequence”This is the source of truth. Every wrapper on this page is a shell-script translation of these lines.
set -euo pipefailVERSION="${VERSION:?VERSION must be set, e.g. v1.2.3}"
apm pack --check-versions --check-clean --json > pack-report.json
for f in build/*.tar.gz .claude-plugin/marketplace.json; do [ -f "$f" ] || continue sha256sum "$f" > "${f}.sha256"done
gh release create "$VERSION" \ build/*.tar.gz \ build/*.tar.gz.sha256 \ .claude-plugin/marketplace.json \ .claude-plugin/marketplace.json.sha256 \ --title "$VERSION" \ --notes-file CHANGELOG.mdWhat each command does:
apm pack --check-versions --check-clean --jsonruns the pack with the release gates enabled.--check-versionsfails if per-package versions disagree withmarketplace.versioning.strategy.--check-cleanfails if the on-diskmarketplace.jsondoes not match what a fresh pack would produce.--jsonwrites a machine-readable summary to stdout; human logs go to stderr.sha256sumproduces one sidecar per artifact. Consumers verify withsha256sum -c <file>.sha256.gh release createuploads the bundle, the marketplace artifact, and the sidecars under one tag. Use whichever release API your forge exposes; the file set is what matters.
Authenticate gh with a token that has contents: write on the
repo. Substitute the equivalent verb for non-GitHub forges
(glab release create, az repos, REST upload).
GitHub Actions
Section titled “GitHub Actions”name: releaseon: push: tags: ["v*"]jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: microsoft/apm-action@v1 with: mode: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}microsoft/apm-action@v1
with mode: release is a convenience wrapper for the canonical
sequence above. It installs the CLI, runs apm pack --check-versions --check-clean --json, generates the sidecars, and
calls gh release create against the pushed tag. Use it when you
want one less script to maintain; use the raw run: form below when
you need to customise any step.
- uses: actions/setup-python@v5 with: { python-version: "3.12" } - run: pip install apm-cli - run: | apm pack --check-versions --check-clean --json > pack-report.json for f in build/*.tar.gz .claude-plugin/marketplace.json; do [ -f "$f" ] || continue sha256sum "$f" > "${f}.sha256" done gh release create "${GITHUB_REF_NAME}" \ build/*.tar.gz build/*.tar.gz.sha256 \ .claude-plugin/marketplace.json* \ --title "${GITHUB_REF_NAME}" --notes-file CHANGELOG.md env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}GitLab CI
Section titled “GitLab CI”release: stage: release image: python:3.12 rules: - if: '$CI_COMMIT_TAG =~ /^v/' script: - pip install apm-cli - apm pack --check-versions --check-clean --json > pack-report.json - | for f in build/*.tar.gz .claude-plugin/marketplace.json; do [ -f "$f" ] || continue sha256sum "$f" > "${f}.sha256" done - | glab release create "$CI_COMMIT_TAG" \ build/*.tar.gz build/*.tar.gz.sha256 \ .claude-plugin/marketplace.json* \ --notes-file CHANGELOG.mdJenkins
Section titled “Jenkins”pipeline { agent any stages { stage('release') { when { tag pattern: "v.*", comparator: "REGEXP" } steps { sh ''' pip install apm-cli apm pack --check-versions --check-clean --json > pack-report.json for f in build/*.tar.gz .claude-plugin/marketplace.json; do [ -f "$f" ] || continue sha256sum "$f" > "${f}.sha256" done gh release create "${TAG_NAME}" \ build/*.tar.gz build/*.tar.gz.sha256 \ .claude-plugin/marketplace.json* \ --notes-file CHANGELOG.md ''' } } }}Azure DevOps
Section titled “Azure DevOps”trigger: tags: include: [refs/tags/v*]pool: { vmImage: ubuntu-latest }steps: - task: UsePythonVersion@0 inputs: { versionSpec: "3.12" } - script: pip install apm-cli - script: apm pack --check-versions --check-clean --json > pack-report.json - script: | for f in build/*.tar.gz .claude-plugin/marketplace.json; do [ -f "$f" ] || continue sha256sum "$f" > "${f}.sha256" done - script: | gh release create "$(Build.SourceBranchName)" \ build/*.tar.gz build/*.tar.gz.sha256 \ .claude-plugin/marketplace.json* \ --notes-file CHANGELOG.md env: GH_TOKEN: $(GITHUB_TOKEN)Troubleshooting the release gates
Section titled “Troubleshooting the release gates”apm pack exit codes you will see in CI:
| Code | Gate | Meaning and fix |
|---|---|---|
| 0 | - | Pack succeeded; ship the artifacts. |
| 1 | runtime | Build or network error. Inspect the JSON report; rerun. |
| 2 | schema | apm.yml is invalid. Fix the manifest before tagging. |
| 3 | --check-versions | Per-package versions disagree with marketplace.versioning.strategy. See Versioning strategies. |
| 4 | --check-clean | Committed marketplace.json does not match a fresh pack. Run apm pack locally, commit the diff, re-tag. |
The gates never write to disk — they only refuse to release.
Recover by running the same apm pack locally without --check-*,
inspecting the diff, and pushing a clean tag.