| name | version-tag |
| description | Create and push a version tag for a package (mobile, cli, or web) from the latest main branch. |
| argument-hint | <package> [version] (e.g., "cli 0.1.0" or just "cli") |
Version Tag
Create and push a git tag for a specific package from the latest main branch.
CLI releases are automated — you usually don't need this skill
Every merge to main that touches cli/** automatically publishes a new patch
version to npm and creates the cli/v* tag via publish-cli.yml. Merge commit
messages can contain [publish minor] or [publish major] for bigger bumps, or
[skip publish] to skip publishing. The committed cli/package.json version is
NOT the source of truth — the workflow stamps the resolved version at build time;
the latest cli/v* tag is authoritative.
For /version-tag cli, skip steps 2–5 entirely. Just dispatch the publish
workflow — it auto-increments when no version is given, guards against existing
tags itself, and creates the tag after a successful publish:
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh workflow run publish-cli.yml --ref main -f version="<version>"
Or, when gh is unavailable, use the GitHub MCP tool
mcp__github__actions_run_trigger with workflow file publish-cli.yml, ref
main, and (optionally) input version: <version>. Report the dispatched run
to the user and finish — no need to watch the publish run to completion.
Mobile releases are automated — you usually don't need this skill
Every merge to main that touches mobile/** automatically creates the next
mobile/v* tag via tag-mobile.yml. Merge commit messages can contain
[publish minor] or [publish major] for bigger bumps, or [skip publish] to
skip tagging. The workflow is tag-only — it does NOT publish an EAS OTA update;
self-hosted installs publish OTA updates from make redeploy on their own
machines with their own EAS credentials.
For /version-tag mobile, skip steps 2–5 entirely. Just dispatch the tag
workflow — it auto-increments when no version is given, guards against existing
tags itself, and creates the tag on main:
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh workflow run tag-mobile.yml --ref main -f version="<version>"
Or, when gh is unavailable, use the GitHub MCP tool
mcp__github__actions_run_trigger with workflow file tag-mobile.yml, ref
main, and (optionally) input version: <version>. Report the dispatched run
to the user and finish — no need to watch the workflow run to completion.
The steps below apply to the web package only.
Arguments
This skill takes one required and one optional argument:
- package (required) — one of
mobile, cli, or web
- version (optional) — a semver version string (e.g.,
0.1.0, 1.2.3). If omitted, automatically increments the patch version of the latest existing tag for the package.
Example usage:
/version-tag cli 0.1.0 — tags cli/v0.1.0
/version-tag cli — if latest cli tag is cli/v0.1.0, tags cli/v0.1.1
Steps
1. Parse and Validate Arguments
Parse the arguments from the input. Validate:
- package must be one of:
mobile, cli, web. If not, print an error and stop.
- If version is provided, it must match semver format (e.g.,
0.1.0, 1.2.3, 0.0.1-beta.1). If not, print an error and stop.
Auto-increment (when version is omitted)
If no version argument is provided, look up the latest existing tag for the package and increment its patch version:
LATEST=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/matching-refs/tags/<package>/v \
--jq '[.[].ref | ltrimstr("refs/tags/<package>/v")] | map(select(test("^[0-9]+\\.[0-9]+\\.[0-9]+$"))) | sort_by(split(".") | map(tonumber)) | last')
- If no existing tags are found (
LATEST is empty), default to 0.0.1.
- Otherwise, split
LATEST on ., increment the third (patch) component by 1, and reassemble. For example, 0.1.0 becomes 0.1.1.
Construct the tag name: <package>/v<version> (e.g., cli/v0.1.0).
2. Check for Existing Tag
Use the GitHub CLI to check whether the tag already exists on the remote:
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/ref/tags/<package>/v<version> 2>/dev/null
If the command succeeds (exit code 0), the tag already exists — print an error and stop. Do NOT overwrite existing tags.
3. Resolve Latest Main SHA
Fetch the SHA of the tip of main from GitHub without checking out anything locally:
SHA=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/ref/heads/main \
--jq '.object.sha')
If the command fails or SHA is empty, print an error and stop.
4. Sync package.json Version
Check whether <package>/package.json exists in the repo and whether its version field matches the target version. If it doesn't match, create a branch, commit the bump there, open a PR, merge it, and wait for the merge to complete before proceeding to step 5. This ensures the tag always points at a commit where package.json has the correct version.
Don't wait on branch protections you can bypass. Once the PR's checks are green, if it's still blocked (e.g., a required code-owner review), check whether your credentials can override branch protections — and if they can, merge immediately instead of waiting for a human. The script below does this automatically via gh pr merge --admin, which merges with admin privileges when the token has bypass rights and fails harmlessly when it doesn't (auto-merge remains armed as the fallback).
FILE_RESP=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/contents/<package>/package.json \
--jq '{sha: .sha, content: .content}')
FILE_SHA=$(echo "$FILE_RESP" | jq -r '.sha')
CURRENT_CONTENT=$(echo "$FILE_RESP" | jq -r '.content' | base64 --decode 2>/dev/null || echo "$FILE_RESP" | jq -r '.content' | base64 -D 2>/dev/null)
PKG_VERSION=$(echo "$CURRENT_CONTENT" | jq -r '.version')
if [ "$PKG_VERSION" != "<version>" ]; then
echo "package.json version is $PKG_VERSION, bumping to <version>..."
BUMP_BRANCH="chore/bump-<package>-v<version>"
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/refs \
-f ref="refs/heads/$BUMP_BRANCH" \
-f sha="$SHA"
UPDATED_CONTENT=$(echo "$CURRENT_CONTENT" | jq --arg v "<version>" '.version = $v')
ENCODED=$(echo "$UPDATED_CONTENT" | base64 | tr -d '\n')
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/contents/<package>/package.json \
-X PUT \
-f message="chore: bump <package>/package.json to <version>" \
-f content="$ENCODED" \
-f sha="$FILE_SHA" \
-f branch="$BUMP_BRANCH"
PR_URL=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh pr create \
--repo supersuit-tech/permission-slip \
--head "$BUMP_BRANCH" \
--base main \
--title "chore: bump <package>/package.json to <version>" \
--body "Auto-generated by \`/version-tag\`. Syncs \`<package>/package.json\` version to match tag \`<package>/v<version>\`.")
echo "Opened PR to bump package.json: $PR_URL"
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh pr merge "$PR_URL" --auto --squash \
--repo supersuit-tech/permission-slip 2>/dev/null || echo "(auto-merge not available — merge manually)"
echo "Waiting for bump PR to merge before creating tag..."
PR_NUMBER=$(echo "$PR_URL" | grep -o '[0-9]*$')
ADMIN_MERGE_TRIED=0
for i in $(seq 1 24); do
PR_STATE=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh pr view "$PR_NUMBER" --repo supersuit-tech/permission-slip --json state --jq '.state')
if [ "$PR_STATE" = "MERGED" ]; then
echo "Bump PR merged."
break
fi
if [ "$ADMIN_MERGE_TRIED" = "0" ]; then
PENDING=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh pr checks "$PR_NUMBER" --repo supersuit-tech/permission-slip 2>/dev/null \
| grep -c 'pending\|in_progress' || true)
if [ "$PENDING" = "0" ]; then
ADMIN_MERGE_TRIED=1
if GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh pr merge "$PR_NUMBER" --squash --admin \
--repo supersuit-tech/permission-slip 2>/dev/null; then
echo "Checks green but PR blocked — merged with admin override."
else
echo "No branch-protection bypass available — waiting for auto-merge."
fi
fi
fi
if [ "$i" = "24" ]; then
echo "::error::Bump PR did not merge within 2 minutes. Merge it manually, then re-run /version-tag."
exit 1
fi
sleep 5
done
SHA=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/ref/heads/main \
--jq '.object.sha')
fi
If the file doesn't exist (404), skip this step silently and proceed with the original SHA.
When gh is unavailable (MCP-only sessions): there is no admin-override flag on the MCP merge tool, but still check whether you can bypass protections by calling mcp__github__merge_pull_request (method squash) once after checks go green. If it succeeds, you had bypass rights — proceed to step 5. If it fails with 405 Repository rule violations (e.g., "Waiting on code owner review"), no bypass is available through MCP: leave auto-merge armed, tell the user exactly what's blocking (and who needs to approve), subscribe to the PR's activity so the merge wakes the session, and resume from step 5 once it merges. Do not fail the flow just because the 2-minute window elapsed — a pending human review is a pause, not an error.
5. Create and Push the Tag
Create the tag reference on GitHub directly via the API — no local branch switching or git push needed. The SHA used here is either the original main SHA (if no bump was needed) or the updated main SHA (after the bump PR merged).
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip \
gh api repos/supersuit-tech/permission-slip/git/refs \
-f ref="refs/tags/<package>/v<version>" \
-f sha="$SHA"
If the command fails with a write-permission error (e.g. "Write access to this GitHub API path is not permitted through this proxy" — some session types cannot write tags or releases at all), print the error output and stop. (For cli and mobile, this never applies — use the workflow dispatch described at the top instead of creating tags directly.)
6. Confirm
Print a summary:
Tagged and pushed: <package>/v<version> -> <SHA>