| name | release-publish |
| description | Publish SkiaSharp packages and finalize the release.
Use when user says "publish X", "finalize X", "tag X", or "finish release X".
This is the FINAL step - after release-testing passes. Publishes to NuGet.org, creates tag, GitHub release, and closes milestone.
Triggers: "publish the release", "push to nuget", "create github release", "tag the release", "close the milestone", "annotate release notes", "testing passed what's next", "finalize 3.119.2", "release is ready".
|
Release Publish Skill
Publish packages to NuGet.org and finalize releases.
⚠️ NO UNDO: This is Step 4 of 4 in the release pipeline (final step). See releasing.md for full workflow.
Pipeline: Step 1: release-branch → Step 2: release-status → Step 3: release-testing → Step 4 (this skill)
⚠️ Branch Protection (COMPLIANCE REQUIRED)
🛑 NEVER commit directly to main or skiasharp branches. This is a policy violation.
| Repository | Protected Branches | Required Action |
|---|
| SkiaSharp (parent) | main | Tags/releases created from release branches, never modify main directly |
| externals/skia (submodule) | main, skiasharp | Never modify directly |
Publishing creates tags on existing release branches — it does NOT modify protected branches.
Workflow Overview
┌────────────────────────────────────────────────────────────────────┐
│ 1. Confirm Versions → Verify packages exist on preview feed │
│ 2. Publish to NuGet.org → Trigger Azure pipeline (manual) │
│ 3. Verify Published → Poll NuGet.org until indexed │
│ 4. Tag Release → Push git tag (ask_user first!) │
│ 5. Create GitHub Release→ Generate notes, set prerelease flag │
│ 6. Annotate Notes → Add platform/contributor emojis │
│ 7. Close Milestone → Stable releases only │
└────────────────────────────────────────────────────────────────────┘
Preview vs Stable differences:
| Step | Preview | Stable |
|---|
| 1. NuGet version | X.Y.Z-preview.N.{build} | X.Y.Z (no build number) |
| 2. Pipeline checkbox | "Push Preview" | "Push Stable" |
| 4. Tag format | vX.Y.Z-preview.N.{build} | vX.Y.Z |
| 5. GitHub Release | --prerelease flag | No flag, attach samples |
| 7. Milestone | Skip | Close milestone |
Step 1: Confirm Versions
⚠️ Semver Version Ordering
When identifying which version to publish, use semver ordering, not alphabetical:
3.119.2 (bare) is NEWER than 3.119.2-preview.3 — it's the stable/final release
- Always verify you are publishing from the correct branch
- If both
release/3.119.2 and release/3.119.2-preview.3 exist, the bare version is the latest
Prerequisite: release-testing must have passed. Versions should be known from testing.
The user should provide:
- Preview: SkiaSharp version with build number (e.g.,
3.119.2-preview.2.3)
- Stable: SkiaSharp base version only (e.g.,
3.119.2) — no build number
⚠️ Stable versions never include a build number. The build number only appears in the prerelease component (e.g., 3.119.2-preview.2.3) or in the internal stable tag (e.g., 3.119.2-stable.3). It is never appended to the base version directly.
If not provided, ask for them using ask_user.
Quick verification — confirm packages exist on preview feed:
dotnet package search SkiaSharp --source "https://aka.ms/skiasharp-eap/index.json" --exact-match --prerelease --format json | jq -r '.searchResult[].packages[].version' | grep "{expected-version}"
dotnet package search SkiaSharp --source "https://aka.ms/skiasharp-eap/index.json" --exact-match --prerelease --format json | jq -r '.searchResult[].packages[].version' | grep "^{base}-stable\."
If missing, STOP and ask user to verify testing was completed.
Step 2: Publish to NuGet.org
Trigger the publish pipeline to push packages to NuGet.org.
Verifying Source Build Before Publishing
Before triggering the publish pipeline, confirm builds completed using the release-status skill:
python3 .agents/skills/release-status/scripts/pipeline-status.py release/{version}
The SkiaSharp pipeline (ID 10789) must show ✅ — this is the pipeline that produced the
packages on the internal feed. See release-status for details.
Pipeline Steps
- Open the NuGet.org publish pipeline
- Click "Run pipeline"
- Select "SkiaSharp" from the radio buttons
- Check "Confirm push to NuGet.org" checkbox
- For stable releases ONLY: Check "Push stable packages" checkbox
- ⚠️ Do NOT check this for preview releases
- Click "Next: Resources"
- In "Pipeline artifacts", click the SkiaSharp artifact selector
- From the branch dropdown, select
release/{version} (the release branch)
- From the pipeline runs list, select the correct build by checking the build number
- Click "Use selected run"
- Click "Run"
Verification During Pipeline Run
⚠️ Before approving the push step, verify BOTH:
- Run name — The pipeline run will rename itself to the version being released. Confirm this matches your expected version.
- Push type — The publish step will indicate "Push Preview" or "Push Stable". Verify this matches your release type:
- Preview release → should show "Push Preview"
- Stable release → should show "Push Stable"
Only approve the push step when both are correct. Wait for pipeline completion (typically 5-10 minutes after approval).
Ask user to follow these steps and wait for completion.
Step 3: Verify Packages Published
Use curl to verify (more reliable than dotnet package search which has version limits):
curl -s -o /dev/null -w "%{http_code}" "https://api.nuget.org/v3-flatcontainer/skiasharp/{version}/skiasharp.nuspec"
curl -s -o /dev/null -w "%{http_code}" "https://api.nuget.org/v3-flatcontainer/harfbuzzsharp/{version}/harfbuzzsharp.nuspec"
If packages not yet indexed, poll until available (NuGet.org can take 5-15 minutes):
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
skia=$(curl -s -o /dev/null -w "%{http_code}" "https://api.nuget.org/v3-flatcontainer/skiasharp/{version}/skiasharp.nuspec")
hb=$(curl -s -o /dev/null -w "%{http_code}" "https://api.nuget.org/v3-flatcontainer/harfbuzzsharp/{version}/harfbuzzsharp.nuspec")
echo "$(date +%H:%M:%S) - SkiaSharp: $skia, HarfBuzzSharp: $hb"
if [ "$skia" = "200" ] && [ "$hb" = "200" ]; then
echo "✅ Both packages available on NuGet.org!"
break
fi
sleep 30
done
Note: Use explicit list 1 2 3... instead of {1..20} brace expansion for better compatibility with async shell execution.
Or manually check: https://www.nuget.org/packages/SkiaSharp/{version}
Step 4: Tag Release
Tag formats:
- Preview:
vX.Y.Z-preview.N.{build} (e.g., v3.119.2-preview.2.5)
- Stable:
vX.Y.Z (e.g., v3.119.2)
git fetch origin
git checkout release/{branch-version}
git pull
git tag {tag}
Confirm with ask_user before pushing tag (cannot be undone):
git push origin {tag}
Step 5: Create GitHub Release
Title Format
| Release Type | Title Format | Example |
|---|
| Preview | Version X.Y.Z (Preview N) | Version 3.119.2 (Preview 2) |
| Stable | Version X.Y.Z | Version 3.119.2 |
| Hotfix Preview | Version X.Y.Z.F (Preview N) | Version 3.119.2.1 (Preview 1) |
| Hotfix Stable | Version X.Y.Z.F | Version 3.119.2.1 |
Finding the Previous Release Tag
Always use --notes-start-tag to explicitly specify the previous release. The auto-selection may pick the wrong tag.
git tag -l "v3.119*" --sort=-v:refname | head -10
| Current Release | Previous Tag (--notes-start-tag) |
|---|
v3.119.2-preview.2.3 | v3.119.2-preview.1.2 (previous preview) |
v3.119.2-preview.1.1 | v3.119.1 (last stable) |
v3.119.2 (stable) | v3.119.2-preview.N.X (last preview of this version) |
v3.119.2.1-preview.1.1 (hotfix) | v3.119.2 (stable being hotfixed) |
Commands
gh release create {tag} \
--title "Version {X.Y.Z} (Preview {N})" \
--generate-notes \
--notes-start-tag {previous-tag} \
--prerelease \
--verify-tag
gh release create {tag} \
--title "Version {X.Y.Z}" \
--generate-notes \
--notes-start-tag {previous-tag} \
--verify-tag
gh release upload {tag} samples.zip
--title sets the release title (use format above)
--generate-notes auto-generates release notes from PRs/commits
--notes-start-tag specifies the previous release to diff from (required)
--prerelease marks as prerelease (preview only)
--verify-tag ensures the tag exists before creating the release
Step 6: Annotate Release Notes with Emojis
After creating the release, annotate each PR line with platform and community emojis.
👉 See references/release-notes.md for:
- Complete emoji reference (platform + contributor)
- Label-to-platform mapping
- Title keyword detection
- Full annotation process and examples
Quick summary:
- Get release body:
gh release view {tag} --json body -q '.body' > /tmp/skiasharp/release/release-body.md
- For each PR: determine platform emoji, add ❤️ for non-mattleibow contributors
- Build sections: Breaking Changes (if any), New Features (if any), What's Changed (all)
- Update release:
gh release edit {tag} --notes-file /tmp/skiasharp/release/release-body.md
Step 7: Close Milestone (Stable only)
Skip for preview releases.
gh api repos/:owner/:repo/milestones --jq '.[] | "\(.number): \(.title)"'
gh api repos/:owner/:repo/milestones/{number} -X PATCH -f state=closed
Error Recovery
Pipeline Fails
| Failure Point | Recovery |
|---|
| Pipeline won't start | Verify branch name, check Azure DevOps permissions |
| Build fails mid-run | Check logs, fix issue on release branch, re-run pipeline |
| Approval rejected | Re-trigger pipeline with correct settings |
| Push step fails | Check NuGet.org status, retry pipeline |
NuGet.org Issues
| Issue | Recovery |
|---|
| Indexing takes >15 min | Normal for large packages. Keep polling. |
| Package shows 404 after publish | Wait up to 30 min. NuGet CDN propagation delay. |
| Wrong version published | Cannot unpublish. Release new corrected version. |
Git/GitHub Issues
| Issue | Recovery |
|---|
| Tag push rejected | Check if tag exists: git ls-remote --tags origin | grep {tag} |
| Tag already exists | Cannot delete. Must use different tag or release new version. |
| GitHub release fails | Re-run gh release create with --verify-tag |
| Release notes wrong | Edit with gh release edit {tag} --notes-file ... |
General Recovery
If you've partially completed and need to resume:
- Check what's done:
gh release view {tag} (release exists?), git ls-remote --tags origin (tag exists?)
- Skip completed steps
- Continue from where you left off
Resources