Skip to content

ci: make main the stable release channel#2312

Merged
alandtse merged 6 commits into
community-shaders:devfrom
alandtse:release-semantic-promote
May 11, 2026
Merged

ci: make main the stable release channel#2312
alandtse merged 6 commits into
community-shaders:devfrom
alandtse:release-semantic-promote

Conversation

@alandtse
Copy link
Copy Markdown
Contributor

@alandtse alandtse commented May 11, 2026

Summary

Splits the release channels so semantic-release can ship hotfixes again. Today dev is both the stable release branch and the integration branch — that breaks the maintenance-branch contract for hotfix/X.Y.x, which requires the hotfix line to be older than the stable branch. The recent hotfix attempt failed with "The release 1.5.2 on branch hotfix/1.5.x cannot be published as it is out of range" for this reason.

New layout:

Branch Role Releases
main Stable release channel vX.Y.Z
dev Integration / RC vX.Y.Z-rc.N
hotfix/X.Y.x Maintenance for older lines only vX.Y.Z on X.Y channel

Patches to the current line: cherry-pick the fix from dev onto main via GitHub's UI "Cherry-pick changes" button → merge → dispatch Release: Semantic Version on main. No hotfix workflow needed.

Promoting RC → stable: dispatch Release: Semantic Version on main with ff_target = <dev SHA>. The workflow fast-forwards main to that SHA, runs semantic-release to cut stable, then fast-forwards 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).

The hotfix workflow keeps doing what it does today, but is now only valid for older lines (after main has shipped a newer minor/major).

Prerequisites before this merges

  • main must exist at the SHA of v1.5.1 on the community-shaders remote. It currently points at stale history from the old doodlum fork; needs a force-push to reset.
  • The obsolete hotfix/1.5.x branch should be deleted after this lands.
  • Wiki Developers.md "Hotfix Release Process" section needs a rewrite (separate PR / commit on the wiki repo).

Test plan

  • Verify on a throwaway dispatch on dev that an RC cut still works post-merge (no release_type input now — dispatch picks behavior from the branch).
  • After main is set to v1.5.1, open a cherry-pick PR from a fix: commit on dev to main, merge it, dispatch Release: Semantic Version on main, confirm v1.5.2 tag + draft release.
  • Exercise ff_target promotion mode at the next minor cut (or on a test repo): dispatch on main with a dev SHA, confirm main FFs, stable tags, dev FFs back, no merge commits in either branch's log.
  • Confirm release-build.yaml and nexus-upload.yaml (dry) fire on the new stable tag.

Summary by CodeRabbit

  • Chores

    • Release dispatch now promotes by targeting a specific commit, validating ancestry, recording a promotion plan, and fast-forwarding main; release tooling runs on the updated history and environment handling was simplified.
    • Dev is reconciled to main only when the promotion source is dev and ancestry permits; hotfix promotions skip dev reconciliation with explanatory notes.
    • Release branches set to main, dev (rc prerelease), and hotfix/*.
    • PR change-detection narrowed to a dedicated build CI file group.
  • Documentation

    • Added release-process guidance covering branch roles, patch/hotfix flow, and semantic-release mappings.

Review Change Stack

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR refactors the release workflow to use a promotion-mode dispatch input (ff_target) instead of release_type, makes .releaserc.js branch configuration static, and adds validation, fast-forward promotion, and conditional post-release dev reconciliation steps that use RELEASE_PAT for authenticated pushes.

Changes

Release Promotion Mode

Layer / File(s) Summary
Static Release Branch Configuration
\.releaserc.js
Removed isRC conditional; branches now always include main, { name: 'dev', prerelease: 'rc' }, and hotfix/*; plugins unchanged.
Dispatch Input
.github/workflows/release-semantic.yaml
Replaced release_type choice with optional ff_target string for promotion mode.
Concurrency Gate
.github/workflows/release-semantic.yaml
Concurrency docs updated to serialize fast-forward promotions on the same ref.
Checkout & Authentication
.github/workflows/release-semantic.yaml
Checkout configured to use secrets.RELEASE_PAT token; bot git identity still set after checkout.
Promotion Target Validation
.github/workflows/release-semantic.yaml
New "Validate ff_target" step (gated on inputs.ff_target != '') fetches refs, verifies target SHA, enforces ancestry for a main fast-forward, determines promotion source (dev or hotfix staging), and writes a promotion plan to the step summary.
Fast-Forward Main Branch
.github/workflows/release-semantic.yaml
New step performs a --force-with-lease push using a tokenized remote with secrets.RELEASE_PAT to advance main to ff_target and resets local main to the promoted commit for semantic-release.
Release Execution & Environment
.github/workflows/release-semantic.yaml
Removed RELEASE_TYPE from semantic-release env and set GITHUB_TOKEN to secrets.RELEASE_PAT.
Post-Release Dev Reconciliation
.github/workflows/release-semantic.yaml
New reconcile step (runs when ff_target set, release published, and source was dev) verifies dev is still an ancestor of promoted main and force-with-lease pushes dev to the new main tip; separate note documents skip for hotfix sources.
PR Change-Detection / Build CI Group
.github/workflows/pr-checks.yaml
Changed check-changes outputs to use build_ci_any_changed and narrowed tj-actions/changed-files to a build_ci category covering selected .github/configs/**.
Docs: Release Model & Patch Flow
.claude/CLAUDE.md
Added mappings from conventional commits to bump behavior, release branch model, patch flow steps, prohibited actions, and link to patch release wiki.

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • doodlum
  • SkrubbySkrubInAShrub
  • davo0411

Poem

🐰 I found ff_target in the night,
I checked the ancestry was right,
I nudged main forward with a gentle hop,
Watched semantic-release tag the top,
A rabbit claps — the pipeline's bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: refactoring the release workflow to make main the stable release channel, which is the core objective described in the PR summary.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@alandtse alandtse changed the title feat(ci): make main the stable release channel ci: make main the stable release channel May 11, 2026
@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/release-semantic.yaml (1)

75-83: 💤 Low value

Quote shell variables in command substitutions for robustness.

While FF_TARGET is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 52c493e and 5b233e0.

📒 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).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/release-semantic.yaml (1)

59-62: 💤 Low value

Consider passing ref_name via 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b233e0 and 8f4cfc1.

📒 Files selected for processing (1)
  • .github/workflows/release-semantic.yaml

alandtse added 2 commits May 10, 2026 17:47
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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/pr-checks.yaml (1)

87-94: ⚡ Quick win

Narrow top-level paths to match build_ci intent

build_ci is scoped well, but the workflow trigger still watches broad .github/workflows/** and .github/configs/**. Consider aligning on.pull_request_target.paths with 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

📥 Commits

Reviewing files that changed from the base of the PR and between 00e4b8d and 51a1701.

📒 Files selected for processing (1)
  • .github/workflows/pr-checks.yaml

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

✅ A pre-release build is available for this PR:
Download

alandtse added 2 commits May 10, 2026 19:13
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.
@alandtse alandtse merged commit 5a59442 into community-shaders:dev May 11, 2026
11 checks passed
alandtse added a commit that referenced this pull request May 12, 2026
SkrubbySkrubInAShrub pushed a commit that referenced this pull request May 14, 2026
IgorAlanAlbuquerque pushed a commit to IgorAlanAlbuquerque/skyrim-community-shaders that referenced this pull request May 29, 2026
SkrubbySkrubInAShrub added a commit that referenced this pull request May 31, 2026
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.
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