ci: make main the stable release channel#2312
Conversation
semantic-release rejects hotfix/X.Y.x releases when the project's stable branch is on the same line — the maintenance-branch contract requires the hotfix line to be older than the stable branch. Today `dev` doubles as both, which deadlocks hotfix releases while dev carries unreleased feats. Split the channels: - main → stable (vX.Y.Z) - dev → rc prereleases (vX.Y.Z-rc.N) - hotfix/X.Y.x → maintenance for older lines only Patches to the current line now land on main directly (cherry-pick PR from dev via GitHub's UI "Cherry-pick changes" button). Hotfix workflow is reserved for past lines, after main has shipped a newer minor/major. Promotion dev → main is folded into this workflow as `ff_target` mode: dispatch on main with a dev SHA, FFs main forward, runs semantic-release to cut stable, FFs dev to absorb the chore(release) commit. Linear history, no PR, no merge commits, no force pushes — all FF pushes guarded by --force-with-lease.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR refactors the release workflow to use a promotion-mode dispatch input ( ChangesRelease Promotion Mode
Sequence Diagram(s)sequenceDiagram
participant User
participant Workflow
participant Origin
participant SemanticRelease
User->>Workflow: dispatch(ff_target)
Workflow->>Origin: fetch main, dev, hotfix refs
Workflow->>Workflow: verify ff_target and determine source
Workflow->>Origin: push --force-with-lease main := ff_target (RELEASE_PAT)
Workflow->>SemanticRelease: run semantic-release (GITHUB_TOKEN=RELEASE_PAT)
SemanticRelease->>Origin: if published && source==dev push dev := promoted-main (RELEASE_PAT)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
No actionable suggestions for changed features. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/release-semantic.yaml (1)
75-83: 💤 Low valueQuote shell variables in command substitutions for robustness.
While
FF_TARGETis validated as a commit SHA beforehand (unlikely to contain special characters), quoting variables inside command substitutions is a defensive shell scripting practice.🔧 Proposed fix
{ echo "## Promotion plan" - echo "- Target SHA: \`$(git rev-parse --short=9 ${FF_TARGET})\`" - RC_TAG=$(git tag --points-at "${FF_TARGET}" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' | head -1 || true) + echo "- Target SHA: \`$(git rev-parse --short=9 "${FF_TARGET}")\`" + RC_TAG=$(git tag --points-at "${FF_TARGET}" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' | head -n1 || true) echo "- RC tag at target: \`${RC_TAG:-<none>}\`" - echo "- Commits being promoted ($(git rev-list --count origin/main..${FF_TARGET})):" + echo "- Commits being promoted ($(git rev-list --count "origin/main..${FF_TARGET}")):" echo "" - git log --oneline "origin/main..${FF_TARGET}" | sed 's/^/ /' + git log --oneline "origin/main..${FF_TARGET}" | sed 's/^/ /' } >> "$GITHUB_STEP_SUMMARY"🤖 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-semantic.yaml around lines 75 - 83, The shell snippet that builds the promotion plan uses unquoted variable expansions (FF_TARGET) inside command substitutions and git commands; update those to use quoted expansions (e.g., "$(git rev-parse --short=9 "${FF_TARGET"}")", "$(git rev-list --count "origin/main..${FF_TARGET}")", and git log --oneline "origin/main..${FF_TARGET}") so that FF_TARGET is always quoted in the command substitutions and git invocations to avoid word-splitting or globbing; adjust the uses of ${FF_TARGET} and RC_TAG in that block accordingly while preserving the existing behavior and output redirection to $GITHUB_STEP_SUMMARY.
🤖 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.
Nitpick comments:
In @.github/workflows/release-semantic.yaml:
- Around line 75-83: The shell snippet that builds the promotion plan uses
unquoted variable expansions (FF_TARGET) inside command substitutions and git
commands; update those to use quoted expansions (e.g., "$(git rev-parse
--short=9 "${FF_TARGET"}")", "$(git rev-list --count
"origin/main..${FF_TARGET}")", and git log --oneline
"origin/main..${FF_TARGET}") so that FF_TARGET is always quoted in the command
substitutions and git invocations to avoid word-splitting or globbing; adjust
the uses of ${FF_TARGET} and RC_TAG in that block accordingly while preserving
the existing behavior and output redirection to $GITHUB_STEP_SUMMARY.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0036b856-c42b-4dfd-a60e-85e919430e71
📒 Files selected for processing (2)
.github/workflows/release-semantic.yaml.releaserc.js
The original ff_target validation required the SHA to be an ancestor of dev. That blocks using a hotfix/X.Y.x branch as a verification staging area for current-line patches — cherry-picked commits get new SHAs that don't live on dev. Relax the check: ff_target may be on dev OR on any origin/hotfix/* branch. Record which branch is the source. Skip the dev FF-reconcile when the source is a hotfix branch (patch-id dedup handles the duplication at the next dev→main promotion; FF-reconcile wouldn't be possible there anyway). This enables one unified patch flow for both current and older lines: Release: Hotfix Candidate → review the auto-built -prNNNN prerelease → merge the candidate PR → dispatch Release: Semantic Version on either main (current line, ff_target = hotfix tip) or the hotfix branch itself (older line, no ff_target).
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/release-semantic.yaml (1)
59-62: 💤 Low valueConsider passing
ref_namevia environment variable for shell safety.Direct interpolation of
${{ github.ref_name }}in shell scripts is generally discouraged. While GitHub ref names are validated and this is unlikely to be exploitable, using an environment variable provides defense-in-depth.♻️ Suggested improvement
- name: Validate ff_target (promotion mode) id: validate if: ${{ inputs.ff_target != '' }} env: FF_TARGET: ${{ inputs.ff_target }} + REF_NAME: ${{ github.ref_name }} run: | set -euo pipefail - if [[ '${{ github.ref_name }}' != 'main' ]]; then - echo "::error::ff_target is only valid when this workflow is dispatched on 'main' (got '${{ github.ref_name }}')." + if [[ "${REF_NAME}" != 'main' ]]; then + echo "::error::ff_target is only valid when this workflow is dispatched on 'main' (got '${REF_NAME}')." exit 1 fi🤖 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-semantic.yaml around lines 59 - 62, The workflow directly interpolates ${{ github.ref_name }} into the shell; instead pass the ref name into the shell via an environment variable (e.g. GITHUB_REF_NAME or REF_NAME) and use that env var in the if check to avoid direct template expansion in shell. Update the step that runs this shell block to set an env mapping (ENV_NAME: ${{ github.ref_name }}) and change the conditional to compare against the environment variable (e.g. if [[ "$ENV_NAME" != 'main' ]]; then ...) so the shell sees only a safe, quoted variable.
🤖 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.
Nitpick comments:
In @.github/workflows/release-semantic.yaml:
- Around line 59-62: The workflow directly interpolates ${{ github.ref_name }}
into the shell; instead pass the ref name into the shell via an environment
variable (e.g. GITHUB_REF_NAME or REF_NAME) and use that env var in the if check
to avoid direct template expansion in shell. Update the step that runs this
shell block to set an env mapping (ENV_NAME: ${{ github.ref_name }}) and change
the conditional to compare against the environment variable (e.g. if [[
"$ENV_NAME" != 'main' ]]; then ...) so the shell sees only a safe, quoted
variable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 52566bfe-5a3e-462a-b632-54ed0085453f
📒 Files selected for processing (1)
.github/workflows/release-semantic.yaml
Agents working in this repo had no way to know main is the stable release channel, dev is rc, and hotfix/X.Y.x is maintenance staging. Without that context, the default assumption was a single-trunk model and PRs would be opened against main instead of dev. Document the branch model, the ff_target promotion mode, the unified patch flow via Release: Hotfix Candidate, and the things not to do without explicit direction (force-push shared branches, hand-bump VERSION, manual v* tags, hotfix dispatch on the current line).
The previous `ci` filter treated any change under .github/workflows/** as build-affecting and triggered cpp + 3 shader-validation jobs even for release workflows that cannot influence build outcomes (this PR hit exactly that — release-semantic.yaml + .releaserc.js + a docs file kicked off the full build matrix). Replace `ci` with `build_ci`, scoped to files that actually drive build behavior: _shared-build.yaml, pr-checks.yaml itself, composite actions, and shader-validation configs. Release-*.yaml, nexus-upload, maint-*, pr-lint/wip no longer trigger rebuilds. Out-of-band file types (.releaserc.js, .claude/**) remain excluded from the workflow's path trigger entirely.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/pr-checks.yaml (1)
87-94: ⚡ Quick winNarrow top-level
pathsto matchbuild_ciintent
build_ciis scoped well, but the workflow trigger still watches broad.github/workflows/**and.github/configs/**. Consider aligningon.pull_request_target.pathswith this narrowed set (or splitting audit logic) so release-only edits don’t run unrelated PR checks.🤖 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/pr-checks.yaml around lines 87 - 94, The workflow currently narrows the build_ci list but leaves the top-level trigger broad; update the on.pull_request_target.paths to match the narrowed build_ci entries (i.e., restrict to the same specific workflow and config patterns referenced by build_ci) or alternatively split the audit/maintenance trigger into a separate workflow so edits outside those specific files (the ones listed in build_ci) don’t run the full PR checks; locate the build_ci list and the on.pull_request_target configuration in the same file and make the path patterns consistent with build_ci (or create a separate trigger for audit-only paths).
🤖 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.
Nitpick comments:
In @.github/workflows/pr-checks.yaml:
- Around line 87-94: The workflow currently narrows the build_ci list but leaves
the top-level trigger broad; update the on.pull_request_target.paths to match
the narrowed build_ci entries (i.e., restrict to the same specific workflow and
config patterns referenced by build_ci) or alternatively split the
audit/maintenance trigger into a separate workflow so edits outside those
specific files (the ones listed in build_ci) don’t run the full PR checks;
locate the build_ci list and the on.pull_request_target configuration in the
same file and make the path patterns consistent with build_ci (or create a
separate trigger for audit-only paths).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e9d16c5f-c1a7-42a8-b8b1-e7a91be8cf9d
📒 Files selected for processing (1)
.github/workflows/pr-checks.yaml
|
✅ A pre-release build is available for this PR: |
Under pull_request_target, workflow definitions and composite actions resolve from the BASE branch ref — so changes to pr-checks.yaml, _shared-build.yaml, or .github/actions/** are not exercised by the PR's own runs. Including them in build_ci ran builds that could not validate what changed. Narrow build_ci to .github/configs/** only — shader-validation configs are the lone CI input that the build actually reads from the PR's checkout. Workflow / composite-action changes still get tested implicitly: the next PR after merge will be the first one to run against the new base-branch workflow.
release-semantic.yaml: Quote shell variables in command substitutions inside the promotion plan step — defensive even though FF_TARGET is validated as a SHA. Switch `head -1` → `head -n1` (POSIX-compliant form). Indent the commit list 4 spaces so it renders as a code block under the preceding markdown list item. pr-checks.yaml: Remove `.github/workflows/**` and `.github/actions/**` from the top-level `paths:` trigger. Both resolve from the base branch under pull_request_target, so changes to them can never be validated by their own PR run. Previously these paths would still fire the unconditional feature-audit-pr job for no useful signal. Consistent with the build_ci narrowing in the previous commit.
(cherry picked from commit 5a59442)
(cherry picked from commit 5a59442)
dev was created as an orphan branch on 2026-05-11 (#2312 migration), leaving it with no common ancestor with main. This -s ours merge brings main into dev's ancestry without altering dev's tree, restoring the release invariant. dev is the source of truth; main's older content is intentionally superseded.
Summary
Splits the release channels so semantic-release can ship hotfixes again. Today
devis both the stable release branch and the integration branch — that breaks the maintenance-branch contract forhotfix/X.Y.x, which requires the hotfix line to be older than the stable branch. The recent hotfix attempt failed with "The release1.5.2on branchhotfix/1.5.xcannot be published as it is out of range" for this reason.New layout:
mainvX.Y.ZdevvX.Y.Z-rc.Nhotfix/X.Y.xvX.Y.ZonX.YchannelPatches to the current line: cherry-pick the fix from
devontomainvia GitHub's UI "Cherry-pick changes" button → merge → dispatchRelease: Semantic Versiononmain. No hotfix workflow needed.Promoting RC → stable: dispatch
Release: Semantic Versiononmainwithff_target = <dev SHA>. The workflow fast-forwardsmainto that SHA, runs semantic-release to cut stable, then fast-forwardsdevto absorb thechore(release):commit. Linear history, no PR, no merge commits, no force pushes (all FF pushes guarded by--force-with-lease).The hotfix workflow keeps doing what it does today, but is now only valid for older lines (after
mainhas shipped a newer minor/major).Prerequisites before this merges
mainmust exist at the SHA ofv1.5.1on the community-shaders remote. It currently points at stale history from the old doodlum fork; needs a force-push to reset.hotfix/1.5.xbranch should be deleted after this lands.Developers.md"Hotfix Release Process" section needs a rewrite (separate PR / commit on the wiki repo).Test plan
devthat an RC cut still works post-merge (norelease_typeinput now — dispatch picks behavior from the branch).mainis set tov1.5.1, open a cherry-pick PR from afix:commit on dev tomain, merge it, dispatchRelease: Semantic Versiononmain, confirmv1.5.2tag + draft release.ff_targetpromotion mode at the next minor cut (or on a test repo): dispatch onmainwith a dev SHA, confirm main FFs, stable tags, dev FFs back, no merge commits in either branch's log.release-build.yamlandnexus-upload.yaml(dry) fire on the new stable tag.Summary by CodeRabbit
Chores
Documentation