Skip to content

Release automatically when the gemspec version changes - #83

Merged
dduugg merged 8 commits into
mainfrom
release-on-version-bump
Sep 29, 2026
Merged

dduugg merged 8 commits into
mainfrom
release-on-version-bump

Conversation

@dduugg

@dduugg dduugg commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #23. Releases now happen when singed.gemspec's version changes, the way the rubyatscale gems that use shared-config's cd.yml release. This is the approach #78 had at 6e0947c, before it moved to release-please. It's here so releases work while #78 waits for approvals; #78 can replace it once it lands.

push_gem.yml already published through RubyGems trusted publishing with rubygems/release-gem, but only by hand, and it has never run. It now runs after CI Test passes on main, and publishes only when the gemspec's version isn't on RubyGems yet, so bumping the version is the release. It can still be dispatched from main to retry, and it skips whatever's already done. A dispatch doesn't wait for CI, so it's meant for when main is green.

  • check job: only lets a push to this repo's main through, since a fork's pull request can come from a branch named main. It reads singed.gemspec through the API at the tested SHA instead of checking it out and evaluating it, so no code from the triggering commit runs in this privileged context. It then asks RubyGems whether the version exists and GitHub whether it has a release. The RubyGems lookup retries on transient errors, since the job runs after every green push to main, not only after a version bump.
  • release job: rake release pushes the current branch along with the tag, so the job checks out main itself rather than a detached HEAD. It first confirms main is still the commit CI tested. No bundler cache, since it publishes. The checkout doesn't persist credentials: release-gem v1.4.1 puts the token in git's credential cache for the tag push.
  • github_release job: creates the GitHub release in a separate job, so a retry can still create it after the gem has shipped. Versions with a letter in them, which RubyGems treats as prereleases, get --prerelease.
  • notify_on_failure: posts to Slack when any job fails, 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.

These are #78's four commits cherry-picked onto main. The resulting push_gem.yml matches 6e0947c's except for three action pins, which keep main's newer versions (harden-runner v2.21.1, checkout v7.0.1, setup-ruby v1.325.0). The first commit's message drops two bullets that #80 and #81 already delivered. The last four commits are follow-ups from review, so the file now also differs from 6e0947c's in those.

Test plan

  • zizmor 1.30.1 with online audits: no findings.
  • actionlint: clean.
  • Gemfile.lock already lists x86_64-linux, and setup-ruby installs the BUNDLED WITH version, so bundle install shouldn't dirty the tree before rake release (the failure described in Automate releasing #23).
  • Fresh Eyes local review: three minor notes, none blocking. A second pass after the follow-up commits found nothing blocking either: its one minor finding doesn't hold, because release-gem fetches tags before rake release, so a retry after a tag push skips the tag and pushes the gem.
    • A failed gh api fetch would stop the step with gh's own error, since the default shell runs bash -e.
    • If main moves between check and release, only the newer version is released; the earlier run aborts on purpose and posts to Slack.
    • --verify-tag fails rather than creating a release without a tag.
  • CI passes on this PR, and CodeQL and zizmor report nothing. The release path itself only runs after a version bump merges to main.

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.
- 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.
@dduugg
dduugg requested a review from a team as a code owner September 29, 2026 22:05
The check job runs after every green push to main, whether or not the
version changed, so a timeout or a 5xx from rubygems.org failed it and
posted a false alarm to Slack. curl --retry retries those, and still
reports a 404 straight away, since a missing version isn't an error.
The error said the run for main's new commit would release the version,
but that run only happens if CI passes on the commit. If it fails, the
version waits for the next green push or a dispatch, so the message now
says so.
A dispatch releases main's tip without checking that CI passed on it,
which suits a retry, and dispatching from another branch skips every job
without saying why. The header comment now says both.
A version like 1.0.0.rc1 passed the version check but got an ordinary
GitHub release, which would have made it Latest. RubyGems treats any
version containing a letter as a prerelease, so the release job now
passes --prerelease for those.
@dduugg
dduugg merged commit e45dd2b into main Sep 29, 2026
15 checks passed
@dduugg
dduugg deleted the release-on-version-bump branch September 29, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Automate releasing

1 participant