Skip to content

fix: create GitHub release against the v-prefixed tag#430

Merged
sherwinski merged 2 commits into
mainfrom
fix/github-release-tag-name
Jun 25, 2026
Merged

fix: create GitHub release against the v-prefixed tag#430
sherwinski merged 2 commits into
mainfrom
fix/github-release-tag-name

Conversation

@sherwinski

Copy link
Copy Markdown
Contributor

One Line Summary

Fix the GitHub Release step so it uses the real v-prefixed git tag instead of creating a stray bare-version tag.

Motivation

The publish workflows delegated GitHub Release creation to the shared OneSignal/sdk-shared/.github/workflows/github-release.yml, which uses a single version value for both the release name and the git tag_name:

await github.rest.repos.createRelease({ tag_name: version, name: version, ... })

This repo passed the bare version (e.g. 3.9.1, with the v stripped), but our tags are v-prefixed (v3.9.1). So createRelease({ tag_name: "3.9.1" }) would target a tag that doesn't exist and create a brand-new lightweight 3.9.1 tag off the default branch — inconsistent with the v3.9.1 convention and disconnected from the published commit. The shared workflow assumes tag == name == version (bare tags), which doesn't hold for this repo.

This is also why the 3.9.1 GitHub Release had to be created manually after the release pipeline ran.

Scope

  • Replaces the shared-workflow github-release job in both publish-svn.yml (v3) and publish-svn-v2.yml (v2) with an inline actions/github-script job.
  • The release is now tagged with github.ref_name (the actual v-prefixed tag that triggered the publish) and titled with the bare version, matching existing releases (v3.9.0 tag / 3.9.0 title).
  • Preserves the existing behavior of sourcing release notes from the merged Release <version> PR body.
  • Adds an idempotency guard: if a release for the tag already exists, the job logs and skips instead of failing.
  • No change to the SVN publish jobs.

Testing

  • Both workflow files validated as parseable YAML.
  • The embedded github-script body passes node --check and runs end-to-end against mocked github/context/core objects.

Notes

  • This intentionally stops using the shared github-release.yml for these two workflows because that workflow cannot express a tag_name that differs from the release name. An alternative would be to add an optional tag_name input to the shared workflow; that's a broader, cross-SDK change and can be done separately if preferred.
  • The 403 "Resource not accessible by personal access token" failure seen on the 3.9.1 run is a separate issue with the GH_PUSH_TOKEN org secret and is being addressed independently.

Checklist

  • Both workflows parse as valid YAML
  • github-script body syntax-checked and smoke-tested
  • Release now uses the v-prefixed tag; title stays bare

The shared github-release workflow used a single version value for both the
release name and the git tag_name. This repo passed the bare version (e.g.
3.9.1), so createRelease targeted a non-existent "3.9.1" tag and would create a
stray tag off the default branch instead of using the real "v3.9.1" tag.

Replace the shared-workflow call in both publish-svn workflows with an inline
github-script job that tags the release with the v-prefixed ref (github.ref_name)
while keeping the bare version as the release title, preserves the merged-release-
PR notes behavior, and skips if a release for the tag already exists.
@sherwinski

Copy link
Copy Markdown
Contributor Author

@claude review once

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM — well-scoped fix that correctly distinguishes the v-prefixed tag from the bare version, with idempotency guard. Inline comments cover nit-level hardening for the release-PR body extraction.

Extended reasoning...

Overview

Both publish workflows (publish-svn.yml for v3, publish-svn-v2.yml for v2) replace the shared OneSignal/sdk-shared/.github/workflows/github-release.yml call with an inline actions/github-script job. The new job uses github.ref_name for tag_name (the actual v-prefixed tag) while keeping the bare version for the release name, which matches the existing release history pattern. It also adds an idempotency guard via getReleaseByTag (404 → proceed, success → skip) so re-runs don't fail.

Security risks

None. Changes are confined to .github/workflows/ files. The script reads PR metadata and creates a release on the same repo using either the existing GH_PUSH_TOKEN secret or the default github.token, with explicit contents: write and pull-requests: read job-level permissions (no escalation). The version/tag values flow through env: rather than direct ${{ }} interpolation into the script body, which avoids the standard github-script injection pitfall.

Level of scrutiny

Low. This is CI-only, doesn't touch the plugin code or the SVN publish jobs, and the v2 + v3 changes are mechanically identical. The fix is narrow (release tag_name was bare instead of v-prefixed) and the rationale — releases were getting created against synthetic bare tags off the default branch — is well-documented in the PR description and matches the visible symptom (the 3.9.1 release had to be created manually).

Other factors

The bug hunter surfaced three nit-level edge cases in the PR-body extraction (startsWith prefix collision on patch versions, null PR body throwing on .split, and the 50-PR pagination cap with silent empty-notes fallback) — all real but none fire in the normal flow, and the inline comments already provide concrete one-line fixes if the author wants to harden further. The PR author has already smoke-tested the script body with mocked github/context/core objects, and the YAML parses. Reviewer sherwinski explicitly tagged me to review.

Comment thread .github/workflows/publish-svn.yml Outdated
Address review feedback on the release-PR-body extraction:
- look up the release PR by the tag's commit SHA via
  listPullRequestsAssociatedWithCommit instead of paging closed PRs, removing
  both the 50-PR window limit and the title prefix-collision (3.9.1 vs 3.9.10)
- require an exact "Release <version>" title match, falling back to the first
  merged associated PR
- guard against a null PR body (was a potential TypeError that would fail the job)
- emit a warning when no release PR is found instead of silently publishing
  empty notes
@sherwinski

Copy link
Copy Markdown
Contributor Author

@claude review once

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM — all three prior findings addressed cleanly in eeae703; the remaining inline note is a nit on the prerelease regex.

Extended reasoning...

Overview

PR replaces a shared-workflow github-release job in both publish-svn.yml (v3) and publish-svn-v2.yml (v2) with an inline actions/github-script step. The fix is to tag the GitHub Release with the actual v-prefixed ref (github.ref_name) while keeping the bare version as the release title — restoring consistency with prior releases (v3.9.0 tag / 3.9.0 title).

Security risks

None of note. The script runs with contents: write and pull-requests: read, scoped to the minimum needed for createRelease and the PR-body lookup. Token falls back to github.token when GH_PUSH_TOKEN is unset. No untrusted input is shelled out; PR title/body are only read.

Level of scrutiny

Moderate. This is release-pipeline plumbing — failures are recoverable (manually create the release), but silent wrong-content (e.g. notes from the wrong PR) would be embarrassing. The hardening work in the follow-up commit (SHA-keyed PR lookup, null body guard, exact title match, core.warning on miss) addresses the realistic failure modes; the script body was exercised against mocked octokit covering exact-match-over-collision, null body, no-PR, fallback, and existing-release-skip paths per the author's note.

Other factors

  • All three of my prior findings (startsWith collision, null body TypeError, 50-PR pagination cap) are addressed in eeae703 — the SHA-keyed listPullRequestsAssociatedWithCommit is the right structural fix and obviates the pagination concern entirely.
  • The remaining inline note (prerelease: /alpha|beta/i.test(version) missing rc/pre/dev/snapshot) is a true nit: only fires for a manually-pushed non-X.Y.Z v-tag, since tag-on-release-merge.yml only auto-tags strict X.Y.Z, and the wrong-flag release is trivially editable.
  • Idempotency guard (getReleaseByTag → 404 → create) is solid; non-404 errors propagate.
  • No production code or security-sensitive paths touched.

name: version,
body,
draft: false,
prerelease: /alpha|beta/i.test(version),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 ⚪ Nit (same code lives in publish-svn-v2.yml:174): prerelease: /alpha|beta/i.test(version) only flags alpha and beta substrings, so a manually-pushed tag like v3.9.1-rc.1 (or -pre/-dev/-next/-snapshot) would match the v3.* trigger and be published as a stable GitHub Release. Today tag-on-release-merge.yml only auto-tags strict X.Y.Z, so this only bites a manually-pushed prerelease tag — but broadening to /-(alpha|beta|rc|pre|dev|snapshot)/i is a one-liner while this code is being touched.

Extended reasoning...

What the bug is

In both publish-svn.yml:145 and publish-svn-v2.yml:174, the GitHub Release is created with:

prerelease: /alpha|beta/i.test(version),

That regex only recognises alpha and beta as prerelease markers. The other conventional identifiers — rc (release candidate, by far the most common after alpha/beta), pre, dev, next, snapshot — are not matched.

Concrete trigger

Step by step for a hypothetical v3.9.1-rc.1:

  1. A maintainer pushes v3.9.1-rc.1. It matches the workflow trigger tags: 'v3.*' and runs publish-svn.yml.
  2. The 'Strip v prefix from tag' step sets bare_version = '3.9.1-rc.1'.
  3. The github-release job runs the inline github-script. /alpha|beta/i.test('3.9.1-rc.1') evaluates to false.
  4. createRelease is called with prerelease: false — published as a stable GitHub Release.

Why existing guards don't catch it

tag-on-release-merge.yml:34 enforces ^chore: Release ([0-9]+\.[0-9]+\.[0-9]+)( \(#[0-9]+\))?$ on the commit subject before creating the v-tag, so the auto-tag flow only produces strict X.Y.Z. The only path to a prerelease v-tag is a maintainer manually pushing one — which is also the same path the PR description acknowledges was used for the 3.9.1 recovery.

Addressing the refutation

One verifier refuted this as a duplicate of a previously-refuted bug and as implausible because (a) the repo's auto-tag flow can't produce prerelease tags and (b) WP.org SVN doesn't have a prerelease concept. Both points are correct in fact but don't dispose of the issue: the prerelease: field clearly exists in the code with the intent of flagging prereleases — it just undercovers the conventional identifier set. The 'SVN trunk publishes a -rc tag as stable anyway' point is a separate, larger problem; it doesn't argue against fixing this line. And the manual-prerelease scenario, while rare, is exactly the kind of edge case that this PR exists to make self-healing.

Impact

Low — only fires when a maintainer manually pushes a non-X.Y.Z v-tag. The wrong-flag GitHub Release is also recoverable (edit the release, flip the checkbox). Hence nit, not normal.

Fix

Broaden the test to cover the standard set:

prerelease: /-(alpha|beta|rc|pre|dev|snapshot)/i.test(version),

or, if the intent is 'any SemVer prerelease', just /-/.test(version) since bare X.Y.Z never contains a hyphen. Apply in both files (publish-svn.yml:145 and `publish-svn-v2.yml:174").

@sherwinski sherwinski requested a review from fadi-george June 24, 2026 21:42
@sherwinski sherwinski merged commit 376f54b into main Jun 25, 2026
10 checks passed
@sherwinski sherwinski deleted the fix/github-release-tag-name branch June 25, 2026 16:57
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.

2 participants