| name | swoole-library-release |
| description | Use when asked to tag, publish, or release Swoole Library for a specific Swoole extension version (e.g. "release swoole/library for v6.2.3", "publish the library release for 6.1.9", "tag swoole-library for the v6.0.1 Swoole release"). |
| argument-hint | ["version"] |
| disable-model-invocation | true |
| allowed-tools | Bash(git:*), Bash(curl:*), Bash(env -u GH_TOKEN gh:*), Bash(grep:*), Bash(head:*), Bash(awk:*), Bash(sort:*), Read, Write, Edit |
Swoole Library Release
Overview
Publishes a swoole/library GitHub release (repo swoole/library, git remote origin) that corresponds to an already-published Swoole extension release. The Swoole extension embeds a snapshot of this library, and every Swoole release records the exact swoole/library commit it embeds inside ext-src/php_swoole_library.h. This skill tags that commit, publishes a matching GitHub release with a changelog relative to the previous library release, and records the same changelog as a new section in CHANGELOG.md.
Input
The input ($ARGUMENTS) is a Swoole release version. Normalize it to vX.Y.Z (add the v prefix if missing). Call it $VERSION throughout. The library tag name is always identical to the Swoole release tag name.
Failure handling
Follow the steps below in order. If any step fails, stop immediately — do not continue to later steps or attempt a workaround — and tell the user clearly, prefixing the message with ERROR:, explaining which step failed and why.
GitHub authentication
The swoole organization rejects fine-grained personal access tokens with a lifetime greater than 366 days. If the GH_TOKEN environment variable is set to such a token, every gh call against swoole/* fails with a policy error. Run all gh commands in this workflow as env -u GH_TOKEN gh ... so gh falls back to the keyring OAuth credential, which the organization accepts.
Step 1 — Prerequisite: the Swoole release must exist
git ls-remote https://github.com/swoole/swoole-src.git "refs/tags/$VERSION"
If this returns no output, the Swoole release does not exist. Report ERROR: Swoole release $VERSION not found in swoole/swoole-src. and stop.
A tag can exist before the GitHub release is published, and the release message links to the Swoole release page. Also check:
env -u GH_TOKEN gh release view "$VERSION" -R swoole/swoole-src --json tagName
If this fails, the tag exists but the release is not published yet. Do not stop — warn the user and continue.
Step 2 — Get the swoole/library commit hash used by that Swoole release
The header comment of ext-src/php_swoole_library.h in the Swoole source records the hash on a line of the form /* $Id: 26e2e98b4bdaf3da1497910c21c44cd96f7a8dc8 */ (near the top of the file):
HASH=$(curl -sfL "https://raw.githubusercontent.com/swoole/swoole-src/$VERSION/ext-src/php_swoole_library.h" | grep -oE '\$Id: [0-9a-f]{40}' | head -1 | awk '{print $2}')
If $HASH is not a 40-character hex string, report ERROR: could not extract the swoole/library commit hash from php_swoole_library.h of Swoole $VERSION. and stop.
Step 3 — Verify the hash exists in this repository
git fetch origin --tags --force
git cat-file -t "$HASH"
--force matters: a plain git fetch --tags silently leaves an existing local tag untouched even when origin has moved it, which would make Step 4 compare against a stale local tag.
The object type must be commit. If not, report ERROR: commit $HASH is not present in the swoole/library repository. and stop.
Step 4 — Create and push the tag
Check the remote and the local tag separately — a previous interrupted run can leave a local tag that was never pushed.
git ls-remote origin "refs/tags/$VERSION" "refs/tags/$VERSION^{}"
git rev-parse -q --verify "refs/tags/$VERSION^{commit}"
For an annotated tag ls-remote prints two lines; the ^{} (peeled) line holds the commit. For a lightweight tag there is one line and it is the commit.
-
Remote tag exists, points to $HASH: skip tagging and continue (idempotent re-run).
-
Remote tag exists, points elsewhere: report ERROR: tag $VERSION already exists on origin and points to a different commit. and stop.
-
No remote tag, local tag exists at $HASH: push it, then continue.
git push origin "$VERSION"
-
No remote tag, local tag exists at a different commit: report ERROR: a local tag $VERSION already exists and points to a different commit; delete or correct it before re-running. and stop. Do not move or delete it automatically.
-
Neither exists:
git tag "$VERSION" "$HASH"
git push origin "$VERSION"
Step 5 — Skip if the release already exists
If env -u GH_TOKEN gh release view "$VERSION" -R swoole/library succeeds, report ERROR: release $VERSION already exists. and stop (the tag work above is still valid and does not need to be undone).
Step 6 — Find the previous release to compare against
Compare against GitHub releases, not bare tags (some tags have no release). Exclude drafts too — gh release list shows drafts to maintainers, and a draft's tag may not exist yet, which would break Step 7:
env -u GH_TOKEN gh release list -R swoole/library --limit 200 --json tagName,isPrerelease,isDraft \
--jq '.[] | select(.isPrerelease == false and .isDraft == false) | .tagName' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'
From that list, pick $PREV as follows (use sort -V for version ordering; only consider versions strictly lower than $VERSION):
- The highest release in the same series (same
X.Y as $VERSION), if any.
- Otherwise (first release of a new series), the highest release overall — i.e. the last release of the previous series.
If no previous release exists at all, note that in the release message instead of a comparison.
Step 7 — Determine the differences
git log --oneline "$PREV".."$VERSION"
git diff --stat "$PREV" "$VERSION"
There is "no difference" when the diff is empty (both tags may even point to the same commit).
Step 8 — Compose the release message
Always start with exactly this line (adjusting the version), followed by a blank line:
Built-in PHP library included in [Swoole $VERSION](https://github.com/swoole/swoole-src/releases/tag/$VERSION).
Then:
-
If there are no differences from $PREV:
This release is the same as Swoole Library [$PREV](https://github.com/swoole/library/releases/tag/$PREV).
-
If there are differences: a Changes since [$PREV](https://github.com/swoole/library/releases/tag/$PREV): line followed by a blank line, then the changelog content described below.
Changelog content (shared by the release message and CHANGELOG.md)
The release message and the matching CHANGELOG.md section (Step 10) carry the same changelog content, written from the commits and diff of Step 7:
- What to include: the major changes since
$PREV — new features, behavior changes, bug fixes, deprecations/removals, and large internal efforts (e.g. a refactoring or modernization pass across the codebase). Leave out minor housekeeping (coding-style fixes touching a few files, CI tweaks, documentation typos, merge commits) unless such a change is big enough to be worth recording (e.g. a coding-style update across many files) or it is the only change in the release.
- Grouping: group the bullets under Keep-a-Changelog-style category labels, each written as a
Label: paragraph followed by a blank line and a bullet list — Added:, Changed:, Deprecated:, Removed:, Fixed:, Security:. Include only the categories that have entries, in that order. See the existing sections of CHANGELOG.md for examples.
- Bullets: plain language, written from a library user's perspective. Reference PRs and issues the way existing entries do (
MR swoole/library#177, Fix issue swoole/library#164, swoole/swoole-src#5595) or link the commit; credit external contributors with (by @username).
Step 9 — Publish the release
Write the message with the Write tool to a scratch file outside the repository (never inside the working tree), then:
env -u GH_TOKEN gh release create "$VERSION" -R swoole/library --verify-tag --latest=false --title "$VERSION" --notes-file <file>
--verify-tag requires the tag to be on origin already, which Step 4 guarantees.
Hard rules:
- NEVER pass
--prerelease.
- ALWAYS pass
--latest=false. The release must never become the "latest" release, because these releases track Swoole versions, not the library's own timeline.
Known GitHub limitation: --latest=false only excludes this release from being chosen as latest at creation time — it does not guarantee another release stays latest. If no release in the repo has ever been explicitly pinned latest, GitHub can still fall back to showing the most recently published release as "Latest" regardless of this flag. After publishing, verify:
env -u GH_TOKEN gh api repos/swoole/library/releases/latest --jq .tag_name
If this prints $VERSION, do not try to silently correct it (gh release edit --latest=false mutates a shared, already-published resource) — call it out in the final report so the user can decide.
Step 10 — Update CHANGELOG.md
Record the release in CHANGELOG.md on master (the file lives on master, not on the tagged commit — the tag usually points to an older snapshot commit):
- Make sure the local
master branch is checked out, clean, and up to date with origin/master.
- Insert a new section above the current top-most version section, headed
## X.Y.Z (YYYY-MM-DD) — version without the v prefix, date being the day the corresponding Swoole release was published (from Step 1's gh release view), falling back to today when that release is not published yet. Its body is the release message minus the Changes since ...: line (the per-version heading already scopes the section): the Built-in PHP library included in ... line, then either the This release is the same as ... line or the grouped changelog content from Step 8.
- If an
## Unreleased section exists at the top of the file, move any of its items that shipped in this release (i.e. their commits are contained in $PREV..$VERSION) into the new section, and delete the ## Unreleased section if that empties it. Items covering commits newer than the tagged commit stay under ## Unreleased, which remains above the new version section.
- Commit with the message
Add CHANGELOG entry for X.Y.Z and push to origin master.
Step 11 — Report
Report a short summary: the tag created (or reused), the commit hash it points to, the previous release used for comparison, the URL of the published release, whether the latest-release check in Step 9 came back clean, and the CHANGELOG.md commit pushed in Step 10.