Skip to content

ci: reconcile dev after hotfix-staging releases#2383

Merged
SkrubbySkrubInAShrub merged 6 commits into
community-shaders:devfrom
alandtse:fix/dev-reconcile-after-hotfix-release
May 20, 2026
Merged

ci: reconcile dev after hotfix-staging releases#2383
SkrubbySkrubInAShrub merged 6 commits into
community-shaders:devfrom
alandtse:fix/dev-reconcile-after-hotfix-release

Conversation

@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub commented May 20, 2026

Summary

Fixes the release-semantic.yaml ff_target flow so a hotfix-staging promotion rebase-reconciles dev instead of skipping. The release commit and its tag now enter dev's ancestry, restoring the lineage invariant.
Adds an Auto-rebase open PRs workflow that runs on every push to dev (including the rebase-forward push above) and rebases open PRs in place. Drafts and no-auto-rebase-labeled PRs are skipped; forks without "Allow edits by maintainers" are surfaced in the summary; conflicting PRs get a needs-rebase label and an explanatory comment.
Documents both reconcile paths and the one sanctioned force-push in CLAUDE.md.

Background — why this matters

When v1.5.2 was released via the hotfix-staging path, dev never absorbed the release commit. The next RC cut on dev (v1.6.0-rc.1) computed its version floor and changelog against the prior tag reachable from dev — v1.5.1 — not v1.5.2. The hotfix was invisible to semantic-release.

The old workflow's "Note dev reconcile skipped (hotfix-staging source)" step claimed patch-id-dedup would handle this at the next dev→main promotion. That assumption is wrong: the chore(release): commit has no patch-id equivalent on dev, so it never lands there. The lineage invariant breaks and stays broken.
How the fix works

git rebase is used (not merge) to preserve linear history on dev.
git rebase automatically drops commits whose patch-id matches an upstream commit, so dev's originals of the cherry-picked fixes are dedup'd against the cherries already on main.
Force-push to dev is --force-with-lease-guarded; concurrent dev pushes surface a remediation block instead of being clobbered.
Rebase conflicts fail the job with a recovery script (rather than silently corrupting state).
The auto-rebase-PRs workflow keeps open PRs current after each rebase-forward.

Secrets / token requirements

RELEASE_PAT is reused for the new workflow (no new secret introduced). Required scopes (classic PAT):

repo — push to protected branches and to fork PR heads via maintainer-edit
workflow — rebased PRs may include .github/workflows/ changes; without this scope GitHub rejects the force-push

Cherrypicked from Alandtses fork, necessary for ci.

Summary by CodeRabbit

  • Chores
    • Expanded development process documentation with detailed branch promotion rules, hotfix handling procedures, and clarifications on agent constraints.
    • Implemented automated pull request rebasing for the development branch, supporting both automatic and manual trigger modes with draft and labeled PR exclusions.
    • Enhanced hotfix release workflow with improved reconciliation mechanisms to synchronize the development branch after promotions, including conflict and push-lease failure handling.

Review Change Stack

alandtse and others added 6 commits May 18, 2026 20:11
The release-semantic.yaml `ff_target` flow skipped its dev reconcile
when the promotion source was a hotfix-staging branch, on the (wrong)
assumption that patch-id-dedup would collapse the duplicates at the
next dev→main promotion. The `chore(release):` commit and its tag have
no patch-id equivalent on dev, so they never entered dev's ancestry.
The lineage invariant "main is an ancestor of dev after reconcile"
silently broke, and the next RC cut on dev (e.g. v1.6.0-rc.1) computed
its version floor and changelog against the wrong prior tag — the
1.5.2 hotfix was invisible.

Fix the reconcile by rebasing dev onto the new main after a hotfix
release. `git rebase` automatically drops dev's originals of the
cherry-picked fixes (patch-id match) and replays any unique dev work
on top, keeping linear history. Push is `--force-with-lease`; lease
failure surfaces a remediation block instead of clobbering concurrent
dev pushes. Rebase conflicts fail the job with a recovery script.

Add an `Auto-rebase open PRs` workflow that runs on every push to dev
(including the rebase-forward push from this fix) and rebases open PRs
in-place. Drafts and PRs labeled `no-auto-rebase` are skipped; forks
without "Allow edits by maintainers" are listed in the summary;
conflicting PRs get a `needs-rebase` label and a comment.

Document both flows and the one sanctioned force-push in CLAUDE.md.

The new workflow reuses the existing RELEASE_PAT secret rather than
introducing a separate token. RELEASE_PAT needs `repo` + `workflow`
scopes (classic PAT): `repo` for protected-branch push and PR-head
push via maintainer-edit; `workflow` so rebased PRs that include
.github/workflows/ changes can be force-pushed without GitHub's
"refusing to allow a Personal Access Token to create or update
workflow" rejection.
Switch the auto-rebase-PRs workflow from the hand-rolled bash loop to
peter-evans/rebase@v3. The original choice mixed up Option 1 (off-the-
shelf action) and Option 2 (custom workflow with conflict labeling and
fork-permission reporting) from the design discussion — Option 1 was
the intent. Drops:

-   ~150 lines of custom shell that iterated PRs, ran rebase in a
    worktree per PR, force-pushed with --force-with-lease, applied
    `needs-rebase` labels, posted PR comments on conflict.
-   The `no-auto-rebase` label exclusion is preserved via the action's
    `exclude-labels` input.
-   Drafts are still excluded (`exclude-drafts: true`).
-   Single-PR mode is preserved by resolving `pr_number` to the
    `<owner>:<branch>` head spec the action accepts.

Behavior trade-offs (intentional with the simpler path):

-   Conflict-skipped PRs no longer get an auto-applied `needs-rebase`
    label or an explanatory comment — the action just skips them. The
    workflow summary documents this bucket so reviewers know what's
    happening.
-   Fork PRs without "Allow edits by maintainers" are silently skipped
    (same as before, just not enumerated in the summary).

If conflict-handling visibility becomes important, layer it on as a
follow-up step that compares `rebased-count` vs open-PR count, or
re-introduce the custom loop. CLAUDE.md updated to reflect the new
(simpler) behavior.

Token remains RELEASE_PAT for the same scope reasons — GITHUB_TOKEN
can't push to fork PR head branches and can't force-push commits that
touch .github/workflows/ without the `workflow` scope.
Two valid findings from CodeRabbit on PR #21:

1. auto-rebase-prs.yaml: validate manual `pr_number` is OPEN and
   targets `dev` before rebasing. Without the guard, a wrong number
   could rebase and force-push a PR that targets a hotfix-staging
   branch (or any other base), silently corrupting it. The validation
   now exits cleanly with a `::warning::` if state != OPEN or
   baseRefName != dev, and an `if:` guard on the rebase step prevents
   the empty-head value from falling through to the all-PRs default
   path when validation rejected the input.

2. release-semantic.yaml: rebase-reconcile push lease failure was
   returning `exit 0`, so the workflow appeared green while dev
   remained unreconciled. That weakens the lineage invariant and
   makes follow-up easy to miss (the next RC on dev would compute
   against the wrong floor — same class of bug that caused the
   v1.5.2 hotfix to be invisible). Changed to `exit 1` with an
   `::error::` so the run goes red and forces a human to read the
   remediation block.

Applied the same `exit 1` treatment to the dev-source FF-reconcile
path on line 181 (not CodeRabbit-flagged because it's pre-existing
code, but the same logical issue and the same invariant break).
Apply the new concise-comments directive to the workflows added in
this PR. Removes ~40 lines of block comments that paraphrased the
adjacent code or repeated input/step descriptions. Behavior unchanged.
Apply the present-tense-comments directive. Reframe two comments that
named past state ("pr_number was given but validation rejected it",
"v1.5.2-style miss") to describe the present invariant the code
protects. Behavior unchanged.
Empty commit to trigger CodeQL on this PR. The default-setup CodeQL
workflow only fires on synchronize events, not reopened, so a push
is required to gate the PR on the new code scan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR implements automated dev reconciliation for the release promotion workflow. It adds a new auto-rebase workflow for open PRs, updates the release workflow to rebase dev onto main after hotfix promotion, and documents the branch lineage invariants governing these reconciliation behaviors.

Changes

Release promotion and dev reconciliation

Layer / File(s) Summary
Release process documentation
.claude/CLAUDE.md
Branch lineage invariant section expanded to distinguish dev → main from hotfix-staging → main promotions and describe rebase-based reconciliation. Force-push/rebase prohibition now includes explicit exception for release workflow reconciliation.
Auto-rebase open PRs on dev push
.github/workflows/auto-rebase-prs.yaml
New workflow rebases open PRs targeting dev on every push or manual dispatch: validates PR state and base via gh CLI when single-PR mode is active, uses peter-evans/rebase@v3 excluding drafts and no-auto-rebase-labeled PRs, and reports rebase count and targeted PR details to job summary.
Release workflow dev reconciliation
.github/workflows/release-semantic.yaml
Updated comments document new hotfix-staging reconciliation path. Dev-source promotion failures now fail loudly with manual remediation guidance. New hotfix-staging reconciliation step rebases dev onto promoted main, handles conflicts/push-lease failures, and records reconciliation status to job summary.

Sequence Diagram(s)

sequenceDiagram
  participant Push as Push to dev
  participant Workflow as auto-rebase-prs
  participant Validate as Validate PR state
  participant Rebase as peter-evans/rebase
  participant Summary as Job Summary
  
  Push->>Workflow: trigger (or workflow_dispatch)
  Workflow->>Validate: resolve PR head spec
  alt single PR mode
    Validate->>Validate: check PR is OPEN & base=dev
    Validate->>Rebase: pass owner:branch if valid
  else all PRs mode
    Validate-->>Rebase: skip validation
  end
  Rebase->>Rebase: rebase onto dev, exclude drafts/no-auto-rebase
  Rebase->>Summary: report rebase count & PR details
Loading
sequenceDiagram
  participant Release as Release trigger
  participant Promote as Promote to main
  participant FFCheck as Fast-forward check
  participant Rebase as Rebase dev
  participant Push as Force-with-lease push
  participant Summary as Job Summary
  
  Release->>Promote: create release commit
  Promote->>FFCheck: can dev FF onto main?
  alt FF possible
    FFCheck->>Summary: record FF reconcile
  else FF not possible
    FFCheck->>Rebase: start rebase
    Rebase->>Rebase: rebase dev onto main
    Rebase->>Push: attempt force-with-lease
    alt push succeeds
      Push->>Summary: record rebase-forward reconcile
    else push-lease rejected
      Push->>Summary: record conflict & failure guidance
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • doodlum
  • jiayev

Poem

🐰 A rabbit's release rhyme:
Branch lines align, rebasing with grace,
Dev catches up in its rightful place.
Hot-fixes flow, no conflicts dismay,
Auto-rebased PRs dance the dev way! ✨

🚥 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 title 'ci: reconcile dev after hotfix-staging releases' accurately describes the main change: adding rebase-reconcile behavior for dev after hotfix releases, which is the core objective addressed across the workflow and documentation updates.
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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 actionlint (1.7.12)
.github/workflows/release-semantic.yaml

could not read ".github/workflows/release-semantic.yaml": open .github/workflows/release-semantic.yaml: no such file or directory

.github/workflows/auto-rebase-prs.yaml

could not read ".github/workflows/auto-rebase-prs.yaml": open .github/workflows/auto-rebase-prs.yaml: no such file or directory

🔧 zizmor (1.25.2)
.github/workflows/release-semantic.yaml

INFO zizmor: 🌈 zizmor v1.25.2
fatal: no audit was performed
invalid input: .github/workflows/release-semantic.yaml

.github/workflows/auto-rebase-prs.yaml

INFO zizmor: 🌈 zizmor v1.25.2
fatal: no audit was performed
invalid input: .github/workflows/auto-rebase-prs.yaml

🔧 YAMLlint (1.38.0)
.github/workflows/auto-rebase-prs.yaml

[Errno 2] No such file or directory: '.github/workflows/auto-rebase-prs.yaml'

.github/workflows/release-semantic.yaml

[Errno 2] No such file or directory: '.github/workflows/release-semantic.yaml'

🔧 Checkov (3.2.529)
.github/workflows/auto-rebase-prs.yaml

2026-05-20 10:56:03,412 [MainThread ] [ERROR] Template file not found: .github/workflows/auto-rebase-prs.yaml
2026-05-20 10:56:03,420 [MainThread ] [ERROR] Template file not found: .github/workflows/auto-rebase-prs.yaml
2026-05-20 10:56:03,453 [MainThread ] [ERROR] Template file not found: .github/workflows/auto-rebase-prs.yaml
2026-05-20 10:56:03,493 [MainThread ] [ERROR] Failed to invoke function /usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner. with .github/workflows/auto-rebase-prs.yaml
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 88, in func_wrapper
result = original_func(item)
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner.py", line 74, in
results = parallel_runner.run_function(lambda f: (f, self._parse_file(f)), files_to_load)

... [truncated 9233 characters] ...

ocess file .github/workflows/auto-rebase-prs.yaml
2026-05-20 10:56:03,615 [MainThread ] [ERROR] Exception traceback:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/main.py", line 647, in run
self.scan_reports = runner_registry.run(
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/runner_registry.py", line 177, in run
for result in parallel_runner_results:
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 118, in _run_function_multiprocess_fork
raise v.internal_exception.with_traceback(v.internal_exception.traceback)
FileNotFoundError: [Errno 2] No such file or directory: '.github/workflows/auto-rebase-prs.yaml'

.github/workflows/release-semantic.yaml

2026-05-20 10:56:03,410 [MainThread ] [ERROR] Template file not found: .github/workflows/release-semantic.yaml
2026-05-20 10:56:03,413 [MainThread ] [ERROR] Template file not found: .github/workflows/release-semantic.yaml
2026-05-20 10:56:03,450 [MainThread ] [ERROR] Template file not found: .github/workflows/release-semantic.yaml
2026-05-20 10:56:03,490 [MainThread ] [ERROR] Failed to invoke function /usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner. with .github/workflows/release-semantic.yaml
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 88, in func_wrapper
result = original_func(item)
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner.py", line 74, in
results = parallel_runner.run_function(lambda f: (f, self._parse_file(f)), files_to_load)

... [truncated 9250 characters] ...

ess file .github/workflows/release-semantic.yaml
2026-05-20 10:56:03,615 [MainThread ] [ERROR] Exception traceback:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/main.py", line 647, in run
self.scan_reports = runner_registry.run(
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/runner_registry.py", line 177, in run
for result in parallel_runner_results:
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 118, in _run_function_multiprocess_fork
raise v.internal_exception.with_traceback(v.internal_exception.traceback)
FileNotFoundError: [Errno 2] No such file or directory: '.github/workflows/release-semantic.yaml'


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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.

Actionable comments posted: 1

🤖 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-semantic.yaml:
- Line 178: The current GitHub Actions conditional uses
steps.semantic.outputs.new_release_published to gate the hotfix reconcile, which
prevents reconcile when semantic-release emits no release; update the if
expression to remove the new_release_published check so the job runs whenever
inputs.ff_target is set and steps.validate.outputs.source is not 'dev' or empty
(i.e., keep inputs.ff_target != '' && steps.validate.outputs.source != 'dev' &&
steps.validate.outputs.source != '' and drop
steps.semantic.outputs.new_release_published == 'true'), referencing the
existing inputs.ff_target, steps.semantic.outputs.new_release_published, and
steps.validate.outputs.source symbols to locate and modify the condition.
🪄 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.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3200c561-6f75-4985-be11-e25f892f247c

📥 Commits

Reviewing files that changed from the base of the PR and between fd20066 and c5c68ea.

📒 Files selected for processing (3)
  • .claude/CLAUDE.md
  • .github/workflows/auto-rebase-prs.yaml
  • .github/workflows/release-semantic.yaml

Comment thread .github/workflows/release-semantic.yaml
@SkrubbySkrubInAShrub SkrubbySkrubInAShrub merged commit 8c13e76 into community-shaders:dev May 20, 2026
5 checks passed
IgorAlanAlbuquerque pushed a commit to IgorAlanAlbuquerque/skyrim-community-shaders that referenced this pull request May 29, 2026
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