Set up or align a GitHub Actions release pipeline for a versioned package, library, CLI, or marketplace action. Use when standardizing repos around the verify-then-release shape: push to main → guardrails → semantic-release tags + publishes → version-bump commit back to main with [skip ci].
99
100%
Does it follow best practices?
Impact
98%
1.55xAverage score across 4 eval scenarios
Passed
No known issues
Push-to-main, semantic-release driven, self-bumping. Only the publish plumbing varies by target (npm, SwiftPM/CocoaPods, Go, Rust, GitHub Action, Homebrew tap). Rust uses release-plz in place of semantic-release; the pipeline shape is identical.
push to main
└─► verify job (lint + typecheck + test + build, on PR and push)
└─► release job (push to main only, !contains [skip ci])
├─► semantic-release: analyze commits, tag, GitHub Release, notes
├─► publish to target (npm / pods / goreleaser / marketplace tag)
└─► @semantic-release/git: commit version bump back to main with [skip ci]Both jobs check out at fetch-depth: 0. The verify job is gated by a cancellable concurrency group; the release job uses a separate non-cancellable group so two releases never race.
.github/workflows/*, release config, tap formula, package metadata, and any failed PR/check logs. If the org has a known-good sibling repo for the same target, read that workflow before choosing an action.main is the release branch, commits follow Conventional Commits, and a publish token for the target registry exists..github/workflows/ci.yml with verify and release jobs per references/workflows.md..releaserc.json, release.config.js, or a "release" block in package.json) per references/semantic-release.md.NPM_TOKEN, COCOAPODS_TRUNK_TOKEN, TAP_GITHUB_TOKEN, etc.) and scope permissions: per job — never broaden the default token.[skip ci] short-circuit to both jobs so the bump commit does not retrigger.GIT_AUTHOR_NAME/GIT_COMMITTER_NAME + emails) so the bump commit is attributed to the release bot, not the last human pusher.feat: / fix: → watch verify→release run → confirm tag, GitHub Release, published artifact, and the chore(release): … [skip ci] commit on main.Load workflow snippets from references/workflows.md, target-specific release shape from references/targets.md, and semantic-release config from references/semantic-release.md only after the package type is known.
Minimal release anchors:
release:
needs: [verify]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') }}
concurrency: { group: release-${{ github.repository }}-main, cancel-in-progress: false }{
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
["@semantic-release/git", { "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" }]
]
}workflow_dispatch, do not weaken needs: [verify].[skip ci] in the message, respected by both jobs' if: guards. Breaking any of those re-triggers the pipeline infinitely.