Conversation
Fixes #23. push_gem.yml already published through trusted publishing with rubygems/release-gem, but only by hand, and it had never run. It now runs after CI Test passes on main, and publishes only when singed.gemspec's version isn't on RubyGems yet, so bumping the version is the release, as with the rubyatscale gems that use shared-config's cd.yml. It still works by hand for retries, and skips a version that's already published. - The check job only lets a push to this repo through, since a fork's pull request can come from a branch named main. - `rake release` pushes the current branch along with the tag, so the release job checks out main itself, rather than a detached HEAD, and first confirms main is still the commit CI tested. - The release job runs `bundle install` without a cache, since it publishes. - Bumps rubygems/release-gem from v1.1.0 to v1.4.1, which fetches tags before releasing and keeps credentials off disk. - Creates the GitHub release, and posts to Slack on failure, like cd.yml. The workflow's filename and the rubygems.org environment stay as they are, since the trusted publisher on RubyGems.org is registered against them.
If `gh release create` failed after the gem was already on RubyGems, a retry skipped everything, since the version was published, so the GitHub release never got created. The check job now also looks for a GitHub release for the version, and a separate job creates it whenever it's missing, whether the gem was just published or already had been.
rubygems/release-gem v1.4.1 stores the token in git's credential cache for the tag push and clears it afterwards, so the checkout doesn't need to leave one behind.
CodeQL flagged actions/cache-poisoning/poisonable-step: the check job checked out the workflow_run head SHA and then ran code from it, since Gem::Specification.load evaluates the gemspec. The job's `if` already limits it to pushes to this repo, but the analysis can't see that. The job now fetches singed.gemspec at that SHA through the API and reads spec.version with a strict pattern, so nothing from the commit runs and the job no longer needs Ruby. If the gemspec ever stops using a string literal for the version, the job fails with an error instead of guessing.
Follows Gusto/rubocop-gusto: release-please keeps a release PR open that bumps the version and CHANGELOG.md from Conventional Commits, and merging it tags the release and creates the GitHub release. The gem is still published with trusted publishing through rubygems/release-gem. - Publishing runs in the same workflow run, when release-please reports release_created. rubocop-gusto publishes on `release: published` with a GitHub App token, because a release created with GITHUB_TOKEN doesn't trigger other workflows; rubyatscale has no such app. push_gem.yml keeps its filename and the rubygems.org environment, which the trusted publisher is registered against. - The version moves from a literal in the gemspec to lib/singed/version.rb, since release-please's Ruby strategy updates a VERSION constant there. It updates singed's own version line in Gemfile.lock too, so there's no separate lockfile job. - bump-minor-pre-major, so a breaking change before 1.0 bumps the minor version rather than jumping to 1.0.0. - A PR title check enforces Conventional Commits, since the squash-merged title drives the version, and Dependabot's commits get a chore(deps) prefix so its PRs pass it. - README and AGENTS.md describe the process. The version-bump check job from the previous approach is gone.
A review turned up several problems with the release-please setup: - Release PRs couldn't be merged. Sorbet is a required check, and release-please pushes its PR with GITHUB_TOKEN, which doesn't trigger workflows. A new job dispatches the CI workflows on the release branch whenever release-please opens or updates the PR, since GITHUB_TOKEN can trigger workflow_dispatch. build.yml, rubocop.yml and sorbet.yml gain that trigger. - The release-please job gets issues: write. The autorelease labels don't exist yet, and GITHUB_TOKEN can't create them with pull-requests: write alone (googleapis/release-please-action#1105). - The publish job checks out the release tag instead of main, and the check that main hadn't moved is gone. release-gem fetches tags before `rake release`, which then skips pushing the branch and tag, so the branch checkout wasn't needed. The check also made "re-run failed jobs" fail for good once anything merged, and it compared against github.sha rather than the tagged commit. With nothing to push, the job only needs contents: read. bundle install runs frozen. - CHANGELOG.md is removed. The Changelog updater found no version header in a file holding only "# Changelog", so it would have left a stray "## Changelog" heading below the first entry. release-please creates the file when it's missing. - harden-runner goes from v2.11.0, which has open advisories, to v2.21.1. checkout and setup-ruby match the pins in the other workflows, and Dependabot now updates GitHub Actions too, with a 7-day cooldown. - The PR title regex rejects nested parentheses in the scope, which release-please can't parse. The error message and README say that perf and revert release a patch, and that a BREAKING CHANGE footer counts as breaking. The README no longer claims the title check enforces anything, since it isn't required. - lib/singed.rb requires singed/version, so Singed::VERSION is defined for apps that install the gem, not only for those loading it by path.
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23. Thanks to @technicalpickles for the groundwork in #20 and #22, and for the write-up in #23.
Summary
Releases are automated with release-please, following Gusto/rubocop-gusto, and still publish with RubyGems trusted publishing (OIDC) through
rubygems/release-gem.mainruns release-please, which keeps a release PR open. The PR bumpslib/singed/version.rb, thesinged (x.y.z)line inGemfile.lock, andCHANGELOG.md, based on the Conventional Commits merged since the last release.vX.Y.Ztag and the GitHub release, with the changelog as its notes.Contributors now need Conventional Commit PR titles, because the squash-merged title decides the version:
feat:bumps the minor version.fix:,perf:andrevert:bump the patch version.chore:,ci:anddocs:don't release anything.A new check flags titles that don't follow the format; it isn't a required check. The README and AGENTS.md describe the process.
How it differs from rubocop-gusto
release_created, rather than in a separate workflow onrelease: published. A release created withGITHUB_TOKENdoesn't trigger other workflows, and rubocop-gusto works around that with a GitHub App token. rubyatscale has no release App, so this avoids needing one.GITHUB_TOKEN, which doesn't trigger workflows. SinceSorbetis a required check, the release PR could never be merged.release-pr-checksjob therefore runsgh workflow runforbuild.yml,rubocop.ymlandsorbet.ymlon the release branch whenever release-please opens or updates the PR.GITHUB_TOKENcan triggerworkflow_dispatch, and the dispatched runs report on the PR's head commit. Those three workflows gain aworkflow_dispatchtrigger.Gemfile.lockline whenpackage-nameis set; its updater skips the lockfile when the name is empty. rubocop-gusto doesn't set a package name, which is why it runsbundle installon its release PR.bump-minor-pre-majoris on, so a breaking change before 1.0 bumps the minor version instead of jumping to 1.0.0.Other changes
singed.gemspectolib/singed/version.rb(Singed::VERSION), which is the file release-please's Ruby strategy updates.lib/singed.rbrequires it, so apps that install the gem getSinged::VERSIONtoo. The built gem is unchanged at 0.3.0.issues: write. Theautorelease: pendingandautorelease: taggedlabels don't exist yet, andGITHUB_TOKENcan't create them withpull-requests: writealone (googleapis/release-please-action#1105).chore(deps)prefix, so its PRs pass the title check. It now updates GitHub Actions as well as the bundle, with a 7-day cooldown.push_gem.ymlkeeps its filename and therubygems.orgenvironment, because the trusted publisher on RubyGems.org is registered against them.mainhas moved;contents: read. release-gem fetches tags beforerake release, and Bundler's release task skips pushing the branch and tag when the tag already exists;bundle installwithout a cache;release_createdandtag_nameoutputs carry over, and the tag checkout means this still works after later merges.release-gemgoes from v1.1.0 to v1.4.1, which fetches tags before releasing and keeps the push token in git's credential cache instead of on disk.harden-runnergoes from v2.11.0, which has open advisories, to v2.21.1.checkoutandsetup-rubynow match the pins in the other workflows.CHANGELOG.mdyet. release-please creates it with the first release. A file holding only# Changelogwould have left a stray## Changelogheading below the first entry.First release
None of the commits since v0.3.0 follow Conventional Commits, so release-please can't see the features or the Ruby floor change that have merged since then. The block below replaces this PR's squash commit for release-please. The release PR it produces should propose 0.4.0 with these notes, and it will open as soon as this PR merges.
Before the first release
rubyatscale/singed, workflowpush_gem.yml, environmentrubygems.org. It's configured at https://rubygems.org/gems/singed/trusted_publishers; the gem's owners aretechpicklesandgusto-open-source.feat: …can therefore land as a commit release-please can't parse, and release nothing.BREAKING CHANGE:line in some commit's body can't change the version unnoticed.GEM_HOST_API_KEYisn't used and can be deleted once a release has gone through.Test plan
--persona pedantic, online audits) is clean onpush_gem.yml,validate-pr-title.ymlanddependabot.yml.build.ymlhas an unrelated existing finding.release-pr --dry-run): the config and manifest parse, and it finds thev0.3.0release at c2792b3. It opens no release PR, because none of the commits since then parse as user-facing.lib/singed/version.rb;singed (0.3.0)whenpackage-nameis set;prs_createdandprare set when it opens or updates the release PR, and not when the PR is unchanged.git fetch --tags --forcebeforerake release, and that Bundler'sreleasetask skipsgit pushwhen the tag already exists.rspec(42 examples),rubocopandsrb tcpass.gem buildstill produces singed 0.3.0 withlib/singed/version.rbincluded.CI Test,RuboCopandSorbetruns from the dispatch, and merging it publishes.