Skip to content

ci: support releases from maintenance branches - #420

Merged
thomson-t merged 3 commits into
mainfrom
thomson-t/maintenance-branch-release-plan
Sep 29, 2026
Merged

thomson-t merged 3 commits into
mainfrom
thomson-t/maintenance-branch-release-plan

Conversation

@thomson-t

@thomson-t thomson-t commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Why

Apps that stay on version 3 of this package need to keep getting fixes after version 4 ships. Version 4 will include breaking changes, so not every app will upgrade straight away. Today the release workflows only release from main, and main will soon hold version 4, so a 3.x fix would have no way out. Once this lands, a 3.x fix can merge into a maintenance branch and be released from there, without moving the default npm install or the "Latest" GitHub release back to 3.x.

Programme

This serves the version 4 release. The plan keeps 3.x on a maintenance/3.x branch; it is internal and cannot be linked. The approach follows the release workflows on the maintenance branch of the Rokt iOS SDK distribution (ROKT/rokt-sdk-ios maintenance/4.x) and of two other SDK repositories. What this package adds is handling for npm's latest tag.

What changes

Before: Release Publish ran only on main. It always published to npm as latest and marked the GitHub release Latest. Release Draft relied on an implicit default for the branch its release pull request targets.

After:

  • Release Draft opens the release pull request against the branch it was run on, so running it on maintenance/3.x prepares a release into maintenance/3.x. It refuses a major bump on any branch other than main.
  • Release Publish also runs when VERSION changes on a maintenance/* branch. It publishes to npm as latest only when the new version is higher than npm's current latest, and as latest-<major> otherwise, for example latest-3. The GitHub release is marked Latest by the same rule. A 3.x patch released before 4.0.0 ships therefore still becomes latest; after 4.0.0 it becomes latest-3. Publishes run one at a time across branches, so each one reads npm's latest after the previous publish has finished.
  • Hardening of Release Draft, fixing zizmor findings already on main: actions/checkout is pinned to a commit and no longer keeps git credentials, the new version reaches shell steps through the environment, and the release GitHub App token is limited to contents and pull requests. Zizmor now also scans maintenance/* branches.

No documentation or changelog moved: the changelog is written by Release Draft at release time, and the release steps are not documented in this repository. The new process is under Rollout.

Start reading at the Resolve npm dist-tag step in release-publish.yml. Left alone on purpose: the changelog action stays on @main, so the fix under Linked work reaches this repository without a bump, and the workflow-wide GITHUB_TOKEN permissions are unchanged, because narrowing them cannot be tested without a real release.

Linked work

Related: ROKT/rokt-workflows#67 fixed the changelog action so 3.x release notes are based on the previous 3.x release. It merged on 2026-09-29, and this repository references the action at @main, so Release Draft already uses the fix.

Rollout

Path: merging changes CI only; nothing is published, and the next release from main behaves as today (latest on npm, marked Latest on GitHub). After the merge, in this order:

  1. A repository admin adds maintenance/* to the branch ruleset main uses, so the branch requires reviews and checks and cannot be force-pushed or deleted. This comes first because a direct push that changes VERSION on an unprotected maintenance branch would publish to npm without review. (Planned.)
  2. Cut maintenance/3.x from the merge commit before any version 4 change reaches main. Each branch runs its own copy of these workflows, so the branch must contain them. (Planned.)
  3. The linked changelog fix, needed before the first 3.x release after 4.0.0 is tagged. (Done: merged on 2026-09-29.)

Feature flags: none.
Turning it off: revert this pull request on main and on maintenance/3.x; the next run uses the old workflows. A version already published to npm cannot be recalled. A wrong npm tag can be moved in seconds by a package maintainer with npm dist-tag add react-native-mparticle@<version> latest.
What we watch: after every release, npm view react-native-mparticle dist-tags and the Latest badge on the GitHub releases page. Today latest is 3.4.0. After 4.0.0, latest must be a 4.x version and latest-3 the newest 3.x; latest on a 3.x version after that means move the tag back and revert this change.

Risks

  • A 3.x release moves npm's latest back from 4.x; prevented by comparing the new version with npm's current latest before publishing, a rule checked locally on five version pairs; we would see it in npm view react-native-mparticle dist-tags.
  • A direct push to an unprotected maintenance/3.x publishes a release nobody reviewed; prevented by adding the branch to the ruleset before the branch is cut (Rollout step 1); we would see a release on npm with no merged release pull request behind it.
  • A release from main and one from a maintenance branch publish at the same moment, both read the old latest, and the one that finishes last wins it; prevented by running the publish job in one shared, non-cancelling concurrency group (raised by the automated Copilot review); a third release started at the same time cancels the waiting one, which shows as a cancelled run that can be re-run.
  • A major bump drafted on the maintenance branch publishes 4.0.0 from 3.x code; prevented by the guard step in Release Draft; we would see that run fail with "Major releases can only be drafted from main".
  • npm is unreachable when the tag is resolved; the step fails and the job stops before anything is published, because Actions runs bash -e; we would see a failed Release Publish run with nothing published.
  • The hardened Release Draft can no longer push the release branch now that checkout keeps no credentials and the app token is narrower; contained because the pull-request action pushes with the app token itself, which keeps contents and pull-request write access, but no real run has exercised it yet; we would see Release Draft fail at "Create release PR", with nothing published.
  • npm trusted publishing rejects a publish from the maintenance branch; not verified, because only package maintainers can see the trusted-publisher settings on npm, though the workflow file name they refer to is unchanged; we would see the first maintenance release fail at "Publish to npm", with nothing published.
  • sort -V orders a pre-release such as 4.0.0-beta.1 above 4.0.0; not addressed, because this package does not publish pre-releases; we would see a wrong tag on the first pre-release.
  • 3.x release notes drafted after 4.0.0 is tagged repeat earlier 3.x entries; prevented by the linked changelog fix, now merged; we would see it in the release pull request, which is read before it merges.

Risk class: higher — a wrong npm tag or Latest flag changes what every new npm install react-native-mparticle gets until a maintainer moves it back, and a published version cannot be recalled.

Who

Written by: an automated coding agent (Claude Code), at an SDK engineer's request, following their plan to keep 3.x on a maintenance branch after version 4.
Code reviewed before opening: no human reviewer. Trunk's pre-commit checks passed on each commit.
Design reviewed before opening: the requesting engineer approved the plan on 2026-09-29, including the latest-3 tag and fixing the changelog action upstream rather than here.
Decision this implements: that plan approval on 2026-09-29; the record is internal and cannot be linked.
Checked: on 2026-09-29, trunk check on the three workflow files (actionlint, checkov, yamllint and prettier) passed. zizmor 1.30.1, the version CI runs, was run before and after, with online audits: see the note below. The npm tag rule was run locally on five version pairs. CI passed on the latest commit: Trunk Check, the zizmor scan, JS Tests, Android unit tests and lint, and the Android and iOS sample app builds. After opening, the automated Copilot review raised one finding, the publish race under Risks, which the second commit fixes; Cursor Bugbot found no issues.
Not checked: a real Release Draft or Release Publish run, because both open release pull requests or publish, and the maintenance branch does not exist yet. The JavaScript and Android test suites were not run locally, because nothing outside .github/workflows changed; CI ran them on this pull request instead (see Checked).

Size

Hand-written: 47 lines added and 4 removed, in 3 files.
Generated: none.

Notes for reviewers

npm tag rule, run locally:

npm latest New version Tag
3.4.0 3.4.1 latest
4.0.0 3.4.1 latest-3
3.4.1 4.0.0 latest
4.2.0 3.10.0 latest-3
3.9.0 3.10.0 latest

3.x and v3 cannot be npm tags, because npm reads them as version ranges; checked with the semver library bundled with npm.

zizmor 1.30.1. With the regular persona that CI uses, release-draft.yml went from 6 findings on main to 1. The one left is the @main reference to the changelog action, kept on purpose. release-publish.yml and zizmor.yml have none. With the strictest (auditor) persona, the total went from 17 to 12, with no new finding.

Files. .github/workflows/release-draft.yml, .github/workflows/release-publish.yml, .github/workflows/zizmor.yml.

🤖 Generated with Claude Code

Release Draft now opens the release pull request against the branch it
runs on, and refuses a major bump anywhere but main. Release Publish
also runs on maintenance/* branches. It publishes to npm as `latest`
only when the version is higher than the current `latest`, and as
`latest-<major>` otherwise, so a 3.x patch cannot pull `latest` back
from 4.x. The GitHub release is marked Latest on the same rule.

Also harden Release Draft: pin actions/checkout to a commit, stop
persisting git credentials, pass the new version to shell steps through
the environment, and limit the release app token to contents and pull
requests. Zizmor now scans maintenance/* branches too.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@thomson-t
thomson-t marked this pull request as ready for review September 29, 2026 20:08
@thomson-t
thomson-t requested a review from a team as a code owner September 29, 2026 20:08
Copilot AI balanced review requested due to automatic review settings September 29, 2026 20:08
@cursor

cursor Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Modifies CI release and npm publishing workflows, where misconfiguration could cause incorrect npm dist-tag assignments or failed releases.

Overview
Updates the release workflows to support drafting and publishing releases from maintenance/* branches alongside main. Release PRs now target the triggering branch, major version bumps are blocked outside main, and CI security scans now run on maintenance branches.

Enhances the publishing workflow with concurrency serialization and dynamic dist-tag resolution. When publishing, the workflow compares the version against npm's current latest to assign either the latest tag or a major-scoped tag (e.g. latest-3), applying the same latest status to GitHub Releases. It also hardens release-draft.yml by unpersisting checkout credentials, scoping GitHub App permissions, and passing versions via environment variables.

Reviewed by Cursor Bugbot for commit c689cad. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Concurrent main and maintenance releases can race and incorrectly roll back npm’s latest tag.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Enables independent releases from maintenance branches while preserving the newest npm and GitHub release as Latest.

Changes:

  • Targets release drafts and publishes to maintenance branches.
  • Selects npm dist-tags and GitHub Latest status by version.
  • Hardens release workflows and expands Zizmor coverage.
File Description
.github/​workflows/​release-draft.yml Targets the selected branch and hardens release drafting.
.github/​workflows/​release-publish.yml Publishes maintenance releases with version-aware tags.
.github/​workflows/​zizmor.yml Scans maintenance branches.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/release-publish.yml
thomson-t and others added 2 commits September 29, 2026 16:20
A release from main and one from a maintenance branch could run at the
same time, both read the old npm `latest`, and both choose `latest`. If
the maintenance publish finished last, `latest` would roll back below
the new major. Put the publish job in one shared, non-cancelling
concurrency group so each run resolves its tag after the previous
publish completes.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop a tooling prefix from two comments and say plainly what each
limitation is.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@thomson-t
thomson-t merged commit 3100b67 into main Sep 29, 2026
12 checks passed
@thomson-t
thomson-t deleted the thomson-t/maintenance-branch-release-plan branch September 29, 2026 20:51
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.

3 participants