fix(release): guard release.yml against prerelease published after stable - #530
Conversation
…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.
Summary by CodeRabbit
WalkthroughThe release workflow adds a ChangesPrerelease staleness guard
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
🔍 PR Validation Summary✅ PR Mergeable — no blocking failures
|
🔍 Lint Analysis
|
🛡️ CodeQL Analysis ResultsLanguages analyzed: ✅ No security issues found. 🔍 View full scan logs | 🛡️ Security tab |
There was a problem hiding this comment.
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.
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
📒 Files selected for processing (2)
.github/workflows/release.ymlsrc/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/.
There was a problem hiding this comment.
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 winDocument the new caller-facing input.
prerelease_branchesis now part of the reusable workflow API, but the provideddocs/release-workflow.mdinputs 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_branchesis 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
📒 Files selected for processing (2)
.github/workflows/release.ymlsrc/config/prerelease-guard/README.md
GitHub Actions Shared Workflows
Description
An audit across 9 LerianStudio repos found
-beta.N/-rc.Ntags being published after the corresponding stablevX.Y.Ztag already existed — a semver ordering violation. Root cause:release.ymlrunssemantic-releaseon every push todevelop/release-candidate, and semantic-release only looks at the history reachable from the pushing branch — it never checks whether thatX.Y.Zwas already promoted to stable onmain. The post-release backmerge (main→develop) is async and can lag behind normal pushes, so a routine merge todevelopright after a release cut re-emits a prerelease for a version that's already stable.This PR adds two gates to
release.yml'spublish_releasejob, both scoped to a newprerelease_branchesinput (default:develop,release-candidate;mainis never affected):backmerge_source(defaultmain) into the current branch via the existingbackmerge-synccomposite 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.semantic-releasecalculates what version would be published; a new composite action,src/config/prerelease-guard, compares that version'sX.Y.Zagainst 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.ymlandjs-release.ymldelegate internally torelease.yml(uses: ./.github/workflows/release.yml), so this fix covers both without any changes to those wrapper workflows.typescript-release.ymlhas 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 workflowperf: Performance improvement (e.g. caching, parallelism, reduced steps)refactor: Internal restructuring with no behavior changedocs: 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, maintenancetest: Adding or updating testsBREAKING CHANGE: Callers must update their configuration after this PRBreaking Changes
None. The new
prerelease_branchesinput has a backward-compatible default; callers pinned to older tags are unaffected until they bump theiruses:ref.Testing
python3 -c "yaml.safe_load(...)"on both files)yamllintlocally — only pre-existing-style warnings (line length / comment spacing), consistent with the rest of the file; no errors@this-branchor the beta tagmainbranch path)app-tokenandLERIAN_CI_CD_USER_*secrets the same way the existing post-release backmerge step does)release.ymland the newprerelease-guardaction were touched)Caller repo / workflow run: Not yet triggered against a live caller — pending follow-up migration of
backoffice-console,matcher,tracer, andproduct-consoleto the tag cut from this PR.Related Issues
Closes #