After the skill PR has been merged, complete the local release wrap-up: install or update the changed skill, clean up old names, complete the Codex links, sync the consumer directories, and independently reconcile using the lock and version. Trigger words:"release skill"、"release X"、"skill release wrap-up"、「release skill」。
Customer-readable description
What this skill can do
| Customer wants | Skill will do | Customer can see |
|---|---|---|
| Release one or more merged skills | Install, update, symlink-sync, and verify lock/version | Six-column release receipt |
| Rename or delete old skills | Remove old installations and all managed directory remnants | Cleaned count and zero-remnant verification |
How customers use it
First confirm the target skill has been merged to the remote repository; this skill does not perform git, PR, merge, push, or publish remote state. Then provide the repository, skill list, and optional old names:
python3 skills/soia-meta-skill-release/scripts/release_skills.py \
--repo <owner/name> \
--skills <skill-a,skill-b> \
--removed <legacy-skill> \
--dry-run
After reviewing the dry-run, remove --dry-run to execute. By default it targets claude-code,codex, which can be overridden with --agents . Version verification resolves the local checkout in the following order:
--repo-dir <repo-path>explicit path;- the current process's
SOIA_SKILL_REPOS_ROOT/<repo-name>; - private YAML:
--config→SOIA_META_SKILL_RELEASE_CONFIG_FILE→~/.config/soia-skills/soia-meta-skill-release/config.ymlinenv.SOIA_SKILL_REPOS_ROOT; - v1 private config directory read-only fallback (will output the suggested
mvmigration command to stderr); - legacy maintainer local directory convention, used only as a deprecated backward-compatibility fallback.
Internally, the repository must still use the skills/<skill-name>/SKILL.md layout. For future new repositories, as long as --repo provides the corresponding any <owner>/<repo-name>, no script changes are needed.
Dependencies and installation
claude plugin marketplace add soia-team/soia-open-skills
claude plugin install soia-meta@soia
When you only want this one skill, you can use the npx route. Note that the skill will land in the shared source of truth ~/.agents/skills; if the plugin is also installed, the same skill will have two indexes and each will drift, so it is recommended to choose one of the two:
npx skills add soia-team/soia-open-skills -g -a '*' -s soia-meta-skill-release -y
| Dependency | Type | Purpose | What to do if missing |
|---|---|---|---|
npx skills |
Hard dependency | Install, remove, update, and maintain the lock | Stop and report the failed step |
soia-meta-sync-skills |
Optional dependency | Only npx mode uses it in step 5 to sync the SOIA and WorkBuddy symlinks; the default plugin mode does not call it |
When using npx mode, install this skill first and retry;plugin mode is unaffected |
| Python 3 | Hard dependency | Execute the release script | Install Python 3 and retry |
| PyYAML | Optional dependency | Read private config.yml |
Pass --repo-dir or use the current process environment variables |
Private information and intermediate data
According to this repository's DATA_STORAGE_SPEC.md, this skill only reads and writes each AI skill installation directory and ~/.agents/.skill-lock.json; it optionally reads the private v2 config.ymlcontaining only the local checkout root. Copy Path configuration template before filling in; do not modify for this one setting .zprofile, nor read or save credentials, caches, or intermediate files. Terminal receipts only show skill name, version, link status, and failed step.
Logs and completion receipts
Stop at the first failed step, and output the steps reached plus the following six-column receipt:
| Skill | Action | Repo version | Installed version | Symlinks (three places) | Result |
|---|---|---|---|---|---|
<skill> |
install/update/remove | <version> |
<version> |
agents / claude / codex | ok / removed / failed |
Workflow
Delivery goes through the plugin marketplace,--install-mode defaults to plugin。By default it will not install any skills into ~/.agents/skills Install any skill—that would coexist with the plugin copy, causing the same skill to have two indexes that drift independently.
plugin mode (default)
- When
--removedis present, clean up.agents、.claude、.soia、.workbuddy、.codex、.pi/agentsix same-name remnants. Rename cleanup runs in both modes: leftover copies under the old name will override plugin updates and keep responding. - Read the repo version and fill it into the receipt; record the installed version as
-, record symlinks asplugin, record the result aspublished。 - Print the steps the user still needs to actually receive the changes: bump the version in both copies of
plugin.json→ the meta-repo regenerates the marketplace manifest and opens a PR for merge → clientplugin update→plugin detailsverification.
ask mode (--install-mode ask, interactive selection)
Requires an interactive terminal. When running, ask the customer whether to install locally: entering y will perform an npx install to the target specified by --agents (the agents list can be overridden on the spot, e.g. claude-code,pi); entering n/Enter is equivalent to plugin mode, publish only without installing. In a non-interactive environment (an agent executing a script), using ask will error out and prompt you to switch to plugin/npx。
npx mode (--install-mode npx, explicit opt-in)
⚠️ Boundaries after the spec was finalized on 2026-08-06: the skill space of every agent must always usephysical installationand must be reproducible(source registered in the lock). Therefore: this mode only allows narrow per-skill commands
npx skills add <repo> -g -a <agent> --copy -s <skill> -y(for multiple skills use repeated-s, a comma-separated list will be treated as a single name; qwen's id isqwen-code);Step 3's fullnpx skills update -g -yis disabled—it broadcasts a reinstall of all skills in the lock to all agent directories, equivalent to the disabled-a '*'; steps 4/5's symlink sync conflicts with the physical installation spec, so they are disabled as well.release_skills.pyhas not yet been reworked for this; do not use its batch steps until the rework is complete.
- Execute each item
npx skills add <repo> -g -a <agents> --copy -s <skill> -y;--agentssupports anynpx skills -aagent id, such asclaude-code、codex、pi(Pi installs into~/.pi/agent/skills)、qwen-code。 - When
--removedis present, run the same-argumentnpx skills remove, and clean up five remnants. - Execute
npx skills update -g -y(disabled, see the warning above; for updates, rerun step 1 for the target skill). - Fill in symlinks for Codex(deprecated; Codex goes through the plugin marketplace).
- Call
soia-meta-sync-skillsto sync soia,workbuddy(deprecated; WorkBuddy goes throughinstall_workbuddy_experts.py)。 - Reconcile
~/.agents/.skill-lock.json: all new skills must come from--repo, and old names must have zero remnants. - Resolve the checkout in the order
--repo-dir→ process environment → private v2 config → read-only v1 config fallback → legacy compatibility directory, resolving checkout in that order, and comparing each item'sSKILL.mdversion with the installed version.
Official release (dev branch model)
Precondition: must have the customer's explicit authorization for this instance. An official release is an outward-facing action—tag, Release, release PR, and marketplace pin refresh all change what external users receive. A customer asking to fix a bug or add a feature does not equalrequesting a release: merging the change into
devcounts as delivery complete; report "already in dev, awaiting your decision on whether to release" and stop. When multiple AIs work in parallel, an uncoordinated release will send out others' unfinished work as well (this actually happened on 2026-08-03). Only--dry-rundry-run rehearsal requires no authorization.
The domain repo uses dual channels:dev handles daily merges (the version carries a -SNAPSHOT declaration of the next target, unchanged during the period,
status identity uses the commit SHA);main always equals the latest official release. When the customer says **"official release X"**, execute:
python3 skills/soia-meta-skill-release/scripts/formal_release.py \
--repo soia-team/<域仓> --repo-dir <本地路径> --summary "<一句话摘要>" --dry-run
after reviewing the dry-run plan, remove --dry-run and execute. The script completes five steps in order, stopping on the first failure:
- finalize PR → dev: each manifest (claude/codex/codebuddy independent tracks) strips
-SNAPSHOT, and inserts the Release Notes beforeCHANGELOG.md—released means updated, same source as the GitHub Release, CHANGELOG follows the plugin cache, so users who installed the plugin can read it offline - fast-forward push dev → main:
git push origin <dev-sha>:refs/heads/main, main and dev point to the same commit v<X.Y.Z>tag is placed on that commit and pushedgh release create(title<插件名> v<X.Y.Z>)- reopen train PR → dev: each manifest +patch enters
-SNAPSHOT
then continue this skill's existing pin refresh and client update process (next section).
How to determine the version number
Reopen train defaults to +patch(1.11.0 → 1.11.1-SNAPSHOT)—right after a release, you don't yet know whether the next version
is a bug fix or a skill addition; defaulting to +minor amounts to predicting "there will definitely be new features," and in practice it will be inflated: v1.11.0 actually
only fixed one display defect, so semantically it should have been 1.10.1. Consistent with Maven release conventions.
Confirm the version by content before release: added a new skill/new capability → manually change dev to minor (e.g.
1.12.0-SNAPSHOT); breaking changes → major. After changing, run the release again; the script uses dev's version as authoritative.
The skill's own version must be bumped separately:plugin.json is the version of the plugin (delivery unit); each
skills/<name>/SKILL.md 's frontmatter version is that skill's own version, and the two are independent.
If you change a skill's body or script, you must bump that skill's version and updated_at—
CI's check_skill_versions.py will block it (missed once on 2026-08-03: skill-release's
script and body were changed, but the skill version stayed at 4.1.0).
Three non-reversible release constraints (all derived from incidents, do not change)
- dev→main uses fast-forward push, not PR merge. All three PR merge methods create commits on main that
dev does not have—squash even breaks the ancestor relationship (the next release will definitely conflict; this actually happened with pkm/media on 2026-08-03), merge leaves a merge commit, rebase rewrites SHAs. Only fast-forward can make main and dev
point to the same commit, so divergence is structurally impossible.
- Premise 1:
mainmust be an ancestor ofdev. The script validates before release; if not satisfied, it aborts and requires first sync main→dev (this will not be satisfied if someone bypasses the process and directly modifies main, or if merge/squash was used historically). - Premise 2: dev HEAD's
auditconclusion must be success. The script explicitly checks check-runs—— fast-forward is not skipping checks, and what is pushed is that commit which has already passed checks. - Repository settings:
main'senforce_adminsmust be disabled, otherwise protected branches reject direct pushes. Other protections (PR requirements,auditmust pass) still apply as usual to ordinary changes.
- Premise 1:
- Between finalization and reopening the train is an invariant broken-window period: after step 1 removes dev's
-SNAPSHOT, until before step 5 reopens, dev is in a violating state. Interruption in this interval will silently leave "dev stopped at the official version number." The script has an assertion as a fallback at the end, butmanual intervention or interruption must be self-checked。 - Release gate: meta-repo
generate_marketplaces.pyreads the manifest of the commit to be pinned, containing-SNAPSHOTdirectly refuses to generate the manifest——SNAPSHOT structurally cannot reach any client.
Supplementary notes from full-ecosystem batch release testing (2026-08-06, learned from releasing 10 repos all at once)
- When the meta-repo itself releases, the finalization PR must include derivative refresh in sync. Domain repos' main release first, meta-repo releases later,
the meta-repo finalization PR's
auditwill successively hit marketplace freshness and skill-pages freshness (in testing, it hit CI twice in a row). Correct approach: on the finalization branch, additionally rungenerate_marketplaces.py、generate_router_index.py、generate_skill_pages.py, and before push, pre-run all of audit's check steps locally until green, then push. - Domain repos' default branch must be main. There was once a domain repo whose default branch pointed to dev, and codex
marketplace addpulled the manifest according to the default branch, installing-SNAPSHOT. Aftergh repo edit <repo> --default-branch maincorrection and reinstall, it returns to the official version. - Before WorkBuddy official installation, all domain repos' local checkout must be switched to main。
install_workbuddy_experts.pycopies the checkout's current branch; after release, dev has already opened the next train (SNAPSHOT), and staying on dev will install SNAPSHOT as the expert version. - Rebuild dev after release (when the customer requests "delete dev and re-pull from main"): open-source repos' dev protection
forbids deletion and also forbids force-push; the process is GET protection config archive → PUT
allow_force_pushes=true→ force-push main+new train → immediately PUT to turn it back off → verify remote sha matches and protection is restored. Free version private repos have no classic protection API (GET returns 403), indicating there was no protection to begin with, so delete/push directly. - Pitfall of manually pushing refspec in zsh:
"$sha:refs/heads/main"'s:rwill be swallowed by zsh as a modifier, producing a corrupted refspec; you must write"${sha}:refs/heads/main"。 - For repos containing feat, before release raise the train to next-minor(the rule already exists above in this section): in batch scenarios,
first group by
git log main..origin/devcounting each repo's feat commits, complete 6 repos' minor bump in one script, then runformal_release.pyrepo by repo, which is more stable than making ad hoc judgments per repo.
Health check: can be run anytime, must be run for inventory
python3 scripts/generate_marketplaces.py --help >/dev/null # 元仓 checkout 内
python3 scripts/check_version_trains.py --repos-root <各仓父目录>
checks two things: ① version train invariants (dev has -SNAPSHOT, main does not) ② whether the next release can merge cleanly.When reporting ecosystem status, these two invariants must be verified; you cannot just copy the version number.——The two missed judgments on 2026-08-03 both stemmed from "only checking whether the values were correct, not verifying whether the rules held."
Trial-install dev (local verification snapshot version)
Trigger words:"Trial-install dev"、"Install dev version locally". The dev snapshot is only for local verification and must never be installed persistently.
- Claude Code (recommended, session-level):
claude --plugin-dir <域仓本地路径>to start a session; the current checkout (the SNAPSHOT version when in dev) is loaded as a plugin, and it is unloaded on exit without polluting the installed state; multiple--plugin-dircan be stacked. When only verifying without starting a session, useclaude --plugin-dir <路径> plugin details <插件名>(--plugin-dirmust come before thepluginsubcommand). - WorkBuddy: after switching the local checkout to dev, run
install_workbuddy_experts.py <插件名>——the script copies the local checkout, and the installed expert is the SNAPSHOT version, which can be directly distinguished by the version number in the UI. - Codex: there is no session-level mechanism;forbiddento persistently install dev/SNAPSHOT——SNAPSHOT will enter the client version comparison path, which is exactly the scenario the release gate intercepts on the marketplace side.
Plugin release and update process (after changes to the domain repository)
After skill changes are merged into the domain repository main, plugin users will not get them immediately——the sha pin in the marketplace manifest still points to the old commit. Perform the following steps to complete the release.
1. Confirm the domain repository changes have been merged
gh api repos/soia-team/<域仓>/commits/main --jq '.sha'
2. Regenerate the marketplace manifest in the meta-repository
The meta-repository main is branch-protected (must go through PR + audit must pass + enforce_admins), so the refresh can only be submitted as a PR and cannot be pushed directly. In the meta-repository checkout, run:
git checkout main && git pull && git checkout -b chore/refresh-marketplace
python3 scripts/generate_marketplaces.py && python3 scripts/generate_router_index.py
The two scripts re-fetch the latest sha of each domain repository main and rewrite .claude-plugin/marketplace.json、.agents/plugins/marketplace.json and the routing index. If git status has no changes, it means the manifest is already up to date; skip to step 5.
3. Submit the PR and merge
git add -A && git commit -m "chore(marketplace): refresh sha pins" && git push -u origin chore/refresh-marketplace
gh pr create --title "chore(marketplace): refresh sha pins" --body "刷新 sha pin 至各域仓最新提交。" --repo soia-team/soia-open-skills
Wait for audit checks to pass, then merge:
gh pr checks <PR号> --repo soia-team/soia-open-skills
gh pr merge <PR号> --squash --delete-branch --repo soia-team/soia-open-skills
audit The marketplace freshness check in will independently recompute the manifest once; if the two sides are inconsistent, it fails——this gate ensures that the published pin indeed points to the current main of the domain repository.
4. Verify the sha pin has been updated
gh api repos/soia-team/soia-open-skills/contents/.claude-plugin/marketplace.json --jq '.content' | base64 -d | python3 -c "import json,sys;print({p['name']:str(p.get('source',{}).get('sha',''))[:12] for p in json.load(sys.stdin)['plugins']})"
Compare with the domain repository sha from step 1; if they match, the manifest is up to date.
Do not use
gh workflow run refresh-marketplace.yml: CI'sGITHUB_TOKENcannot push directly to the protected main, and the PR it creates will not trigger theauditcheck (GitHub suppresses it to prevent recursion); neither path works. Marketplace refresh is part of the release action and is explicitly completed by this process.
5. Guide clients to update
Claude Code:First record the installed manifest, and reconcile at the end——plugin update will fail directly for plugins that are not installed, and uninstall/reinstall-type operations can also easily miss installations:
claude plugin list | grep soia > /tmp/claude-soia-before.txt && cat /tmp/claude-soia-before.txt
claude plugin marketplace update soia
claude plugin update <域插件名>@soia
Reconcile at the end, and restore missing ones one by one with plugin install :
claude plugin list | grep soia | diff /tmp/claude-soia-before.txt -
After updating, Claude Code must be restarted for it to take effect. Users who have enabled autoUpdate will automatically complete these two steps on the next startup.
claude plugin details <名>For plugins from a private marketplace, the marketplace suffix must be included (<名>@<市场>); without it, it reports "not installed", which is easily misjudged as the plugin being lost. To check installation status, useplugin listis more reliable.
Codex:First record the current installed manifest——the cache will be deleted below, and deleting at the wrong granularity will also uninstall other plugins from the same marketplace:
codex plugin list | grep '@soia' > /tmp/soia-installed-before.txt && cat /tmp/soia-installed-before.txt
Only delete the marketplace staging(marketplace add will reuse the old clone; if you don't delete it, you won't be able to pull the newly added resource files):
rm -rf ~/.codex/.tmp/marketplaces/soia
The plugin cache only deletes the target one,soia is the marketplace name, not the plugin name,rm -rf ~/.codex/plugins/cache/soia it will remove all8 pluginsunder that marketplace at once:
rm -rf ~/.codex/plugins/cache/soia/<域插件名>
codex plugin marketplace add soia-team/soia-open-skills
codex plugin add <域插件名>@soia
Finally, compare against the installation checklist, to confirm there is no collateral loss; if anything is missing, add them back one by one with plugin add :
codex plugin list | grep '@soia' | diff /tmp/soia-installed-before.txt -
Codex has no automatic update mechanism and must be done manually. Skipping the step of deleting the staging area will result in "the command reports success, but the content is still the old version" — this was actually encountered on 2026-07-27: the corp marketplace staging was stuck on an old version without assets/icon.svg ,composerIcon pointed to a nonexistent file, the interface fell back to a generic icon, and during troubleshooting it was misjudged as an incorrectly written path.
6. WorkBuddy experts (only do this when the customer is using WorkBuddy)
WorkBuddy is an Electron desktop app,with no CLI— there is no workbuddy plugin install,
nor is there a marketplace channel that can point to our GitHub. So this step is done by a script; do not look for an equivalent command:
python3 skills/soia-meta-skill-release/scripts/install_workbuddy_experts.py --dry-run
After confirming the plan, execute (with no arguments it installs all; you can also provide only the plugin names to install):
python3 skills/soia-meta-skill-release/scripts/install_workbuddy_experts.py
The script copies the domain repo checkout into my-experts/plugins/<插件名>, then calls the official WorkBuddy
register_expert.py registration. Three empirically tested constraints dictate that this is the only way:
| Constraint | Empirical conclusion |
|---|---|
| Directory | Self-built experts only recognize the hardcoded my-experts, which appears in 38 places in the app; if placed elsewhere, they do not show up |
| Symlink | does not work. The official validate_expert.py resolves the path resolve(), and after following it, it determines that it is "not under the expert directory" |
| Remote | Marketplace entry source can only be a path string, with no sha pin layer;expert/install deep links require sharecode, going through the official cloud |
After installationthe customer must be asked to restart WorkBuddy, otherwise the new expert will not appear under [Experts · Skills · Connectors → My Experts].
Verification: after summoning that expert, ask "how many usable skills do you have"; all skills from that domain should be present; when not summoned, they are not present.
7. Reclaim old version caches
Both clients, after plugin update only add new version directories,and do not reclaim the old ones; Claude's .in_use marker is also unreliable (in practice, both the old and new versions of the same plugin carry this file). If not cleaned up, they will accumulate linearly and interfere with troubleshooting — using find to find resources will match multiple version directories, andls will produce absurd results when counting skills.
python3 skills/soia-meta-skill-release/scripts/prune_plugin_cache.py
After confirming the dry run is correct, execute:
python3 skills/soia-meta-skill-release/scripts/prune_plugin_cache.py --apply
Keep the highest value by semantic version and delete the rest; directories with non-semantic versions (such as the official plugin's latest) are skipped entirely. The cache can be re-pulled from the marketplace at any time, so even deleting the wrong one just means downloading it one more time.
8. Verification
claude plugin list
codex plugin list
Confirm that the target plugin version has changed and the status is enabled.
Domain repo and plugin comparison
| Domain repo | Plugin name |
|---|---|
| soia-open-dev-skills | soia-dev |
| soia-open-dev-design-skills | soia-dev-design |
| soia-open-pkm-vault-skills | soia-pkm-vault |
| soia-open-media-content-skills | soia-media-content |
| soia-open-cwork-office-skills | soia-cwork-office |
| soia-open-edu-course-skills | soia-edu-course |
| soia-open-env-skills | soia-env |
| soia-open-skills | soia-meta |
Boundaries and verification
- Only perform post-merge local cleanup; pre-release merge is done by the caller.
--dry-rundoes not execute any commands or file writes, and only outputs a plan receipt.- Forward tests should mock in a temporary HOME
subprocess, covering command order, fail-fast, cleanup of the five old names, Codex supplementary links, the lock branch, and dry-run.
Release prerequisite: cross-repo installation section checkup
The audit_skills.py in each domain repo differ from one another,private-skills and some don't even have that script, so the rule "the installation section covers three first-class hosts"cannot be uniformly verified in each repo's CI. The portal repo's CI only uses
--self to check its own few skills.
Before release, run the full batch once locally (requires working copies of each domain repo under the same parent directory):
python3 <soia-open-skills>/scripts/check_install_sections.py --repos-root <各仓的父目录>
It scans <repo>/skills/* and <repo>/*/skills/*(covering private-skills'
workspace/ and harness/ subrepo directories); a non-zero exit means some skill is missing a first-class host.