Skip to content

chore: publish releases from CI with RubyGems trusted publishing - #242

Open
felipefreitag wants to merge 2 commits into
mainfrom
chore/release-workflow
Open

felipefreitag wants to merge 2 commits into
mainfrom
chore/release-workflow

Conversation

@felipefreitag

@felipefreitag felipefreitag commented Sep 16, 2026

Copy link
Copy Markdown
Member

Maintainers can now publish a release by pushing a v* tag. The Release workflow runs the checks, builds the gem, pushes it to RubyGems.org and creates the GitHub release.

Before, a release meant a GitHub release made in the UI, then gem push from a laptop with a personal API key. RubyGems recorded no link between the gem file and the commit that produced it, and no version of the gem carries an attestation.

Now every push carries a sigstore attestation that names the repository, the workflow and the commit. The GitHub release exists only after the push succeeds. A tag that does not match the version in lib/resend/version.rb fails before anything is pushed.

How to reach it. Merge a version bump, then tag the merge commit with v plus the version and push the tag. Watch the Release run. A manual run of the workflow from the Actions tab executes the checks and the build only.

Not covered. RubyGems must know the workflow first. A gem owner adds a trusted publisher for resend/resend-ruby, workflow release.yml, environment release. Until then the publish job fails and releases stay manual. This mirrors resend-node #1097 and resend-python #281.

DEV-2133


Summary by cubic

Pushing a v* tag now publishes a release from CI with RubyGems trusted publishing. The workflow checks the tag against lib/resend/version.rb, runs RuboCop and specs, builds the gem, smoke-tests the installed gem version against the tag, pushes it to RubyGems.org with a sigstore attestation, and only then creates the GitHub release. This replaces the old manual gem push flow from a laptop, so releases now carry proof of the repository, workflow, and commit that produced them.

Setup

  • A RubyGems owner must add resend/resend-ruby as a trusted publisher for the release.yml workflow and release environment before publishing can succeed.

Behavior

  • A manual workflow run from the Actions tab only runs checks and builds; it doesn't publish. Runs from a tag ref still verify the tag.
  • A tag that doesn't match lib/resend/version.rb fails before anything is pushed.
  • Tags with a pre-release version create a prerelease GitHub release and don't mark it as latest.

Written for commit d264036. Summary will update on new commits.

Review in cubic

A push of a v* tag now runs the Release workflow. It checks the tag
against Resend::VERSION, runs rubocop and the specs, builds the gem,
pushes it to RubyGems.org through the OIDC trusted publisher with a
sigstore attestation, then creates the GitHub release.

The publish job needs a trusted publisher on rubygems.org for
resend/resend-ruby, workflow release.yml, environment release. Until an
owner adds it the publish job fails and releases stay manual.

Mirrors resend-node 1097 and resend-python 281. DEV-2133
@cubic-dev-ai

cubic-dev-ai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Running ultrareview automatically — This workflow automates production gem publishing and GitHub releases using trusted OIDC permissions, tag/version validation, and concurrency controls, so subtle CI or supply-chain mistakes could publish an incorrect or compromised artifact.. I'll post findings when complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview completed in 3m 35s

1 issue found across 1 file

Confidence score: 3/5

  • .github/workflows/release.yml uses a constant concurrency group, so a second tag pushed during an active release can cancel the earlier pending run and leave that version unpublished; key the group by github.ref or remove this concurrency setting.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yml">

<violation number="1" location=".github/workflows/release.yml:10">
P2: When a second tag is pushed while another release is running, this constant group can replace the earlier pending tag run, so that version is never published. Key the group by `github.ref` or remove this concurrency group so each tag run is retained.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

workflow_dispatch:

concurrency:
group: release

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a second tag is pushed while another release is running, this constant group can replace the earlier pending tag run, so that version is never published. Key the group by github.ref or remove this concurrency group so each tag run is retained.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 10:

<comment>When a second tag is pushed while another release is running, this constant group can replace the earlier pending tag run, so that version is never published. Key the group by `github.ref` or remove this concurrency group so each tag run is retained.</comment>

<file context>
@@ -0,0 +1,101 @@
+  workflow_dispatch:
+
+concurrency:
+  group: release
+  cancel-in-progress: false
+
</file context>
Suggested change
group: release
group: release-${{ github.ref }}

The guard and both publish gates now check the ref type instead of the
event name, so a manual run from a tag verifies the tag as well. The
smoke test compares the installed gem version to the tag. The version
shape accepts the dotted prerelease form this gem has shipped before,
such as 1.3.0.alpha.1.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Confidence score: 2/5

  • In .github/workflows/release.yml, manually dispatching the workflow for a tag now satisfies both release-job conditions, so it can publish the gem and create a GitHub release unexpectedly; require a push event in addition to the tag ref for both jobs.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yml">

<violation number="1" location=".github/workflows/release.yml:71">
P1: When the workflow is manually dispatched against a tag, both release jobs now run because `github.ref_type` is `tag`, publishing the gem and creating a release. Require a push event as well as a tag ref in both job conditions so manual runs remain checks/build only.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


publish-rubygems:
needs: ci
if: github.ref_type == 'tag'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the workflow is manually dispatched against a tag, both release jobs now run because github.ref_type is tag, publishing the gem and creating a release. Require a push event as well as a tag ref in both job conditions so manual runs remain checks/build only.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 71:

<comment>When the workflow is manually dispatched against a tag, both release jobs now run because `github.ref_type` is `tag`, publishing the gem and creating a release. Require a push event as well as a tag ref in both job conditions so manual runs remain checks/build only.</comment>

<file context>
@@ -65,11 +64,11 @@ jobs:
   publish-rubygems:
     needs: ci
-    if: github.event_name == 'push'
+    if: github.ref_type == 'tag'
     runs-on: ubuntu-latest
     timeout-minutes: 15
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant