From gh-guard
Automates PR-based release flow for Rust crates with signed tags, branch protection, CI polling, local checks, and crates.io publishing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gh-guard:release-automationThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers a PR-based release flow that works with branch protection rules, signed tags, and CI-triggered publishing.
This skill covers a PR-based release flow that works with branch protection rules, signed tags, and CI-triggered publishing.
Developer GitHub crates.io
│ │ │
├─ scripts/release.sh 0.2.0 │ │
│ │ │
├─ Local checks (deny, │ │
│ test, clippy, dry-run) │ │
│ │ │
├─ Create release/v0.2.0 ──→│ │
├─ Bump Cargo.toml ────────→│ │
├─ Push branch ────────────→│ │
├─ gh pr create ───────────→│── CI runs on PR │
│ │ │
├─ Poll for checks start │ │
├─ gh pr checks --watch ───→│── Waits for CI │
│ │ │
├─ gh pr merge ────────────→│── Squash merge │
├─ git pull origin main │ │
├─ git tag -s v0.2.0 ──────→│ │
├─ git push origin v0.2.0 ─→│── Tag push triggers: │
│ │ 1. publish (crates.io) ────→│
│ │ 2. provenance (SLSA L3) │
│ │ 3. release (GitHub) │
│ │ │
The release script verifies before doing anything:
X.Y.Zgit config user.signingkey must be setmainorigin/mainCargo.toml version differs from targetv${VERSION} tag not already presentBefore creating the PR, run local checks to catch issues early:
cargo deny check advisories bans licenses sources
cargo test --locked --all-features --quiet
cargo clippy --all-features -- -D warnings
cargo publish --dry-run --locked
This avoids wasting CI time on PRs that would fail basic checks.
GOTCHA: gh pr checks --watch has a race condition — it returns immediately with success if no checks have been registered yet (which happens in the few seconds between PR creation and GitHub processing the workflow triggers).
Solution: Poll for check existence before watching:
echo "Waiting for CI checks to start..."
for i in $(seq 1 30); do
if gh pr checks "$PR_NUMBER" --json name --jq '.[0].name' &>/dev/null 2>&1; then
break
fi
sleep 2
done
echo "Waiting for CI checks to complete..."
gh pr checks "$PR_NUMBER" --watch
Tags should be signed to verify the release was created by an authorized maintainer:
git tag -s "v$VERSION" -m "Release v$VERSION"
SSH signing setup (recommended):
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global tag.gpgSign true
GPG signing setup:
git config --global user.signingkey <GPG_KEY_ID>
git config --global tag.gpgSign true
The release script handles branch protection gracefully:
if ! gh pr merge "$PR_NUMBER" --squash --delete-branch; then
echo "Standard merge failed. --admin bypasses branch protection."
read -rp "Merge with --admin? [y/N] " ADMIN_CONFIRM
if [[ "$ADMIN_CONFIRM" =~ ^[Yy]$ ]]; then
gh pr merge "$PR_NUMBER" --squash --delete-branch --admin
else
echo "Aborting. Fix the issue and merge manually."
git checkout main
exit 1
fi
fi
--admin merge (bypasses protection — requires explicit consent)Note: Admin merges lower the Scorecard Code-Review score. For best scores, have another maintainer review the version bump PR.
If your org or repo has tag protection rules:
Cargo.tomlorigin/mainIf a publish workflow fails (e.g., crates.io outage), use workflow_dispatch instead of gh run rerun:
# Preferred: retrigger with current workflow file
gh workflow run publish.yml -f tag=v0.1.5
# Avoid: gh run rerun uses the ORIGINAL workflow file, not current
Why not gh run rerun: It re-runs with the original workflow file from the commit, not the current one. If you fixed a bug in the workflow file, the rerun will use the old buggy version.
The publish workflow's PUBLISH_TAG env var resolves the tag from either trigger type, so all verification steps work correctly with workflow_dispatch.
After the signed tag is pushed, CI handles everything:
.intoto.jsonl attachedMonitor the pipeline:
gh run watch $(gh run list --limit 1 --workflow publish.yml --json databaseId -q '.[0].databaseId')
See templates/release.sh for the complete release script.
npx claudepluginhub sbom-tool/gh-guardGates a release on the trunk by verifying required CI checks are green, then cuts a semver tag + GitHub release or opens a release PR. Use for releasing, cutting tags, or verifying CI gates.
Implements SLSA L3 build provenance for Rust crates via three-job GitHub Actions pipeline: publish (tests/package/hash), provenance (signed attestation), release (attach to GitHub release).
Manages full GitHub release workflow: versioning, CHANGELOG sync, release notes, tagging, CI monitoring, and verification. Enforces constraints to prevent using release for CI testing.