Skip to content

fix(release): guard release.yml against prerelease published after stable - #530

Merged
bedatty merged 2 commits into
developfrom
fix/prerelease-staleness-gates
Jul 1, 2026
Merged

fix(release): guard release.yml against prerelease published after stable#530
bedatty merged 2 commits into
developfrom
fix/prerelease-staleness-gates

Conversation

@bedatty

@bedatty bedatty commented Jul 1, 2026

Copy link
Copy Markdown
Contributor
Lerian

GitHub Actions Shared Workflows


Description

An audit across 9 LerianStudio repos found -beta.N/-rc.N tags being published after the corresponding stable vX.Y.Z tag already existed — a semver ordering violation. Root cause: release.yml runs semantic-release on every push to develop/release-candidate, and semantic-release only looks at the history reachable from the pushing branch — it never checks whether that X.Y.Z was already promoted to stable on main. The post-release backmerge (maindevelop) is async and can lag behind normal pushes, so a routine merge to develop right after a release cut re-emits a prerelease for a version that's already stable.

This PR adds two gates to release.yml's publish_release job, both scoped to a new prerelease_branches input (default: develop,release-candidate; main is never affected):

  1. Pre-sync gate — before calculating the next version, merge backmerge_source (default main) into the current branch via the existing backmerge-sync composite action. Most pushes are no-ops (target already contains source). If a direct merge isn't possible (conflict), the release is skipped for this run (job succeeds, nothing published) with a ::warning:: annotation instead of failing — a human needs to resolve the pending sync PR first.
  2. Defensive guard — a dry-run of semantic-release calculates what version would be published; a new composite action, src/config/prerelease-guard, compares that version's X.Y.Z against the highest stable tag already released and fails the run if the prerelease would land on an already-stable version. This catches residual races even after gate 1 (e.g. concurrent pushes).

Both go-release.yml and js-release.yml delegate internally to release.yml (uses: ./.github/workflows/release.yml), so this fix covers both without any changes to those wrapper workflows. typescript-release.yml has its own separate flow and is out of scope for this change.

Type of Change

  • fix: Bug fix in a workflow (incorrect behavior, broken step, wrong condition)
  • feat: New workflow or new input/output/step in an existing workflow
  • perf: Performance improvement (e.g. caching, parallelism, reduced steps)
  • refactor: Internal restructuring with no behavior change
  • docs: Documentation only (README, docs/, inline comments)
  • ci: Changes to self-CI (workflows under .github/workflows/ that run on this repo)
  • chore: Dependency bumps, config updates, maintenance
  • test: Adding or updating tests
  • BREAKING CHANGE: Callers must update their configuration after this PR

Breaking Changes

None. The new prerelease_branches input has a backward-compatible default; callers pinned to older tags are unaffected until they bump their uses: ref.

Testing

  • YAML syntax validated locally (python3 -c "yaml.safe_load(...)" on both files)
  • Ran yamllint locally — only pre-existing-style warnings (line length / comment spacing), consistent with the rest of the file; no errors
  • Triggered a real workflow run on a caller repository using @this-branch or the beta tag
  • Verified all existing inputs still work with default values (new input has a default; existing steps' conditions were only extended, not replaced, for the main branch path)
  • Confirmed no secrets or tokens are printed in logs (guard/sync steps reuse the existing app-token and LERIAN_CI_CD_USER_* secrets the same way the existing post-release backmerge step does)
  • Checked that unrelated workflows are not affected (only release.yml and the new prerelease-guard action were touched)

Caller repo / workflow run: Not yet triggered against a live caller — pending follow-up migration of backoffice-console, matcher, tracer, and product-console to the tag cut from this PR.

Related Issues

Closes #

…able

Add two gates before the real semantic-release publish step on
prerelease branches (develop, release-candidate):
1. Sync backmerge_source into the current branch before calculating
   the next version, so semantic-release sees the latest stable
   history instead of a stale branch. Falls back to skipping the
   release (not failing) when a PR is needed to resolve conflicts.
2. A defensive dry-run + prerelease-guard action that fails the run
   if the calculated prerelease version's X.Y.Z already has a
   published stable release.

New prerelease_branches input (default: develop,release-candidate)
controls which branches get these checks; main is unaffected.
@bedatty
bedatty requested a review from a team as a code owner July 1, 2026 18:29
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added configurable prerelease branch support to the release workflow.
    • Introduced a prerelease “staleness/validity” guard that verifies the next prerelease won’t conflict with the latest published stable release.
  • Bug Fixes
    • Improves prerelease handling by conditionally syncing changes for prerelease branches and only publishing when the sync result allows it.
    • Blocks prereleases that would overlap an already-published stable version.
  • Documentation
    • Added documentation for the prerelease guard action.

Walkthrough

The release workflow adds a prerelease_branches input and restructures publish_release to classify prerelease branches, conditionally sync backmerge sources, dry-run semantic-release for version calculation, and gate the final release step on a prerelease guard. A new composite action validates prerelease versions against published stable tags.

Changes

Prerelease staleness guard

Layer / File(s) Summary
Prerelease branch classification input
.github/workflows/release.yml
Adds prerelease_branches to workflow_call.inputs with a default branch list used for prerelease classification.
Backmerge sync and conditional release flow
.github/workflows/release.yml
Removes the earlier Node.js/npm and semantic-release setup from this location, then adds prerelease classification, conditional backmerge syncing, prerelease dry-run version calculation, prerelease-guard invocation, and a conditional final Semantic Release step.
Prerelease guard composite action
src/config/prerelease-guard/action.yml, src/config/prerelease-guard/README.md
Adds a composite action that compares the prerelease base version against the highest stable tag, and documents its inputs, permissions, and usage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the prerelease guard fix in release.yml.
Description check ✅ Passed The description follows the template with Description, Type of Change, Breaking Changes, Testing, and Related Issues sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/prerelease-staleness-gates

Comment @coderabbitai help to get the list of available commands.

@lerian-studio lerian-studio added size/S PR changes 50–199 lines workflow Changes to one or more reusable workflow files config Changes to repository configuration composite actions (src/config/) composite Changes to any composite action manifest (src/**/*.yml) labels Jul 1, 2026
@lerian-studio

lerian-studio commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Validation Summary

✅ PR Mergeable — no blocking failures

Check Status Blocking
Source Branch ✅ success yes
PR Title ✅ success yes
PR Description ✅ success yes
PR Size ✅ success no
Auto Labels ✅ success no
PR Metadata ✅ success no

🔍 View workflow run

@lerian-studio

lerian-studio commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🔍 Lint Analysis

Check Files Scanned Status
YAML Lint 2 file(s) ✅ success
Action Lint 1 file(s) ✅ success
Pinned Actions 2 file(s) ✅ success
Markdown Link Check 1 file(s) ✅ success
Spelling Check 3 file(s) ✅ success
Shell Check 2 file(s) ✅ success
README Check 2 file(s) ✅ success
Composite Schema 1 file(s) ✅ success
Deployment Matrix no changes ⏭️ skipped

🔍 View full scan logs

Comment thread .github/workflows/release.yml Fixed
Comment thread .github/workflows/release.yml Fixed
Comment thread .github/workflows/release.yml Fixed
Comment thread .github/workflows/release.yml Fixed
@lerian-studio

lerian-studio commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🛡️ CodeQL Analysis Results

Languages analyzed: actions

✅ No security issues found.


🔍 View full scan logs | 🛡️ Security tab

@coderabbitai coderabbitai 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 260-263: The warning step in release.yml interpolates untrusted
GitHub expressions directly inside the shell script, which can be expanded
before Bash runs. Update the “Warn about pending backmerge sync” step to pass
the dynamic values through env variables first, then reference those env vars in
the run block. Keep the change localized to the warning step that uses
steps.pre_sync.outputs.pr-url, inputs.backmerge_source, github.ref_name, and
matrix.app.name.
- Around line 245-254: The prerelease pre-sync step is incorrectly reusing the
post-release backmerge setting, so callers with backmerge_mode set to pr can
block prerelease publishing and direct can take the wrong conflict behavior.
Update the pre_sync step in release.yml to use a separate pre-sync mode for the
backmerge-sync action, or force it to direct with PR fallback, and keep this
change tied to the pre_sync and backmerge-sync@v1 configuration.

In `@src/config/prerelease-guard/action.yml`:
- Around line 1-19: The new composite action defined by
prerelease-guard/action.yml is missing its required adjacent README, so add
src/config/prerelease-guard/README.md to document the action’s purpose, inputs,
and usage. Keep the README aligned with the action’s public contract by
describing calculated-version, source-branch, target-branch, and the prerelease
validation behavior so users can understand how to wire it up.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 917cd1ff-e2ef-479b-a7a4-a47379772a7c

📥 Commits

Reviewing files that changed from the base of the PR and between 1377689 and e8e2d22.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • src/config/prerelease-guard/action.yml

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread src/config/prerelease-guard/action.yml
- Decouple the pre-sync gate's merge mode from backmerge_mode: force
  direct-with-pr-fallback so callers configured with backmerge_mode: pr
  (which would always open a PR and thus always skip publishing) or
  backmerge_mode: direct (which would hard-fail the job on conflict
  instead of gracefully skipping) aren't affected by an unrelated
  config choice meant for the post-release backmerge.
- Pass dynamic values (app name, branch names, PR url) through env
  vars instead of interpolating them directly into the warning step's
  run: block, avoiding template-expansion/shell-injection risk.
- Add the missing README.md for the new prerelease-guard composite
  action, matching the convention of every sibling action under
  src/config/.
@lerian-studio lerian-studio added size/M PR changes 200–499 lines documentation Improvements or additions to documentation and removed size/S PR changes 50–199 lines labels Jul 1, 2026

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)

87-91: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new caller-facing input.

prerelease_branches is now part of the reusable workflow API, but the provided docs/release-workflow.md inputs table does not list it. Add the input, default, and stale-prerelease behavior so callers can configure this safely. Based on the provided release workflow docs input table, prerelease_branches is currently absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 87 - 91, The reusable workflow
API now exposes prerelease_branches, but the release workflow docs still omit
it. Update the inputs table in docs/release-workflow.md to add
prerelease_branches, including its default value and the behavior for prerelease
branches when backmerge_source cannot be merged or when a calculated prerelease
would collide with an existing stable X.Y.Z release. Refer to the workflow input
definition named prerelease_branches in release.yml so the documentation matches
the caller-facing contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/release.yml:
- Around line 87-91: The reusable workflow API now exposes prerelease_branches,
but the release workflow docs still omit it. Update the inputs table in
docs/release-workflow.md to add prerelease_branches, including its default value
and the behavior for prerelease branches when backmerge_source cannot be merged
or when a calculated prerelease would collide with an existing stable X.Y.Z
release. Refer to the workflow input definition named prerelease_branches in
release.yml so the documentation matches the caller-facing contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 64a51c1e-dc55-4fa2-ac63-aaac97fea6f5

📥 Commits

Reviewing files that changed from the base of the PR and between e8e2d22 and ca4cd0d.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • src/config/prerelease-guard/README.md

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

Labels

composite Changes to any composite action manifest (src/**/*.yml) config Changes to repository configuration composite actions (src/config/) documentation Improvements or additions to documentation size/M PR changes 200–499 lines workflow Changes to one or more reusable workflow files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants