Skip to content

ci: reconcile dev after hotfix-staging releases#21

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

ci: reconcile dev after hotfix-staging releases#21
alandtse merged 6 commits into
devfrom
fix/dev-reconcile-after-hotfix-release

Conversation

@alandtse
Copy link
Copy Markdown
Owner

@alandtse alandtse commented May 19, 2026

Note: This PR was originally opened against alandtse/skyrim-community-shaders/pull/21. The fork was renamed to alandtse/open-shaders since — GitHub redirects the old URL transparently, the branch SHA is unchanged. See PR #22 for the rebrand commits.

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

RELEASE_PAT was verified in the throwaway test/verify-release-pat-scopes workflow run on 2026-05-19: X-OAuth-Scopes: repo, workflow, all read/write probes passed (label create returns HTTP 201, delete returns 204).

Test plan

  • Confirm RELEASE_PAT includes workflow scope (verified — see throwaway run)
  • One-shot: manually rebase-forward dev onto main to absorb v1.5.2 (closes the current gap left by the prior bug)
  • Trigger Auto-rebase open PRs via workflow_dispatch with no pr_number against a test scenario to confirm summary table renders
  • On next hotfix release, observe release-semantic.yaml job summary contains the Dev reconciled (rebase-forward) section
  • Confirm auto-rebase-prs.yaml fires on the rebase-forward push and shows per-PR results

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@alandtse has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 22 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2048e115-3db0-4b6b-9e60-db3bd3cd6513

📥 Commits

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

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

Walkthrough

This PR implements rebase-based branch management automation across release workflows. It introduces a new GitHub Actions workflow to auto-rebase pull requests against dev, enhances developer guidance on branch lineage invariants and release constraints, and updates the release workflow to actively reconcile dev after hotfix promotions using force-push-with-lease.

Changes

Release Branch Management Automation

Layer / File(s) Summary
Release Management Guidance
.claude/CLAUDE.md
Expands the "Branch lineage invariant" section with concrete descriptions of sanctioned promotion behavior for dev → main and hotfix-staging → main, and adds an explicit exception to the "Things agents should not do" rule for rebase-reconcile remediation during hotfix promotions.
Auto-Rebase Workflow Setup & PR Discovery
.github/workflows/auto-rebase-prs.yaml
New workflow metadata, triggers (push to dev, manual dispatch), concurrency configuration, repository checkout with full history and PAT authentication, git author setup, and filtering logic that discovers eligible open PRs targeting dev, respects draft/no-auto-rebase labels, enforces fork maintainer-edit permissions, and determines the PR set.
Auto-Rebase Workflow Rebase & Results
.github/workflows/auto-rebase-prs.yaml
Core rebase execution in a temporary detached worktree with conflict detection; on conflict, aborts/cleans up, labels the PR with needs-rebase, and posts manual remediation instructions. On success, force-pushes rebased commits with --force-with-lease, removes the needs-rebase label, cleans up worktree, and publishes a markdown summary including aggregate counts and per-PR outcomes to GITHUB_STEP_SUMMARY.
Release Workflow Dev Reconciliation
.github/workflows/release-semantic.yaml
Expands header documentation for hotfix promotion (patch-id duplicate dropping, force-with-lease requirement), updates ff_target guidance to reflect rebase-reconcile behavior, and replaces the prior "dev reconcile not applicable" summary step with a new step that checks out dev, rebases onto the promoted main, fails on conflict after emitting a manual recovery guide, and attempts force-with-lease push with fallback messaging and non-fatal exit on lease rejection.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 Branch management takes care and grace,
Auto-rebase keeps pace with dev space,
Hotfix promotions reconcile with flair,
Force-with-lease ensures we're fair,
Release workflows now dance through the air!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding dev reconciliation after hotfix-staging releases, which is the core objective of 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
  • Commit unit tests in branch fix/dev-reconcile-after-hotfix-release

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

@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: 2

🤖 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/auto-rebase-prs.yaml:
- Around line 81-87: When handling a manual run with ONLY_PR, validate the
fetched PR (PR_JSON) before assigning PRS: after running gh pr view into
PR_JSON, parse and check that baseRefName equals "dev" and state equals "OPEN"
(and optionally isDraft is false) and only then set PRS to '[.]' for processing;
if the checks fail, exit with an error or set PRS to an empty list to avoid
rebasing the wrong PR. Ensure this validation is done immediately after the gh
pr view/PR_JSON step and references the ONLY_PR variable, PR_JSON and PRS
symbols so manual runs are guarded to open PRs targeting dev only.

In @.github/workflows/release-semantic.yaml:
- Around line 234-248: The workflow currently treats a failed rebase-reconcile
push (the if block guarding the git push --force-with-lease="dev:${CURRENT_DEV}"
... "HEAD:refs/heads/dev") as success by calling exit 0; change that final exit
0 to exit 1 so the job fails on lease-rejection, ensuring the
GITHUB_STEP_SUMMARY warning and the echo "::warning::Dev moved during release —
manual reconcile required." surface as a failed run and do not leave dev
unreconciled silently.
🪄 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

Run ID: 164fa980-950f-4cf7-984e-ab8164b018e0

📥 Commits

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

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

Comment thread .github/workflows/auto-rebase-prs.yaml Outdated
Comment thread .github/workflows/release-semantic.yaml
alandtse added 2 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.
@alandtse alandtse force-pushed the fix/dev-reconcile-after-hotfix-release branch from b39a044 to 9c01a4b Compare May 19, 2026 03:11
@alandtse alandtse changed the title fix(ci): reconcile dev after hotfix-staging releases ci: reconcile dev after hotfix-staging releases May 19, 2026
alandtse added 3 commits May 18, 2026 21:28
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.
@alandtse alandtse closed this May 20, 2026
@alandtse alandtse reopened this May 20, 2026
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>
@alandtse alandtse merged commit 5117702 into dev May 20, 2026
9 checks passed
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.

1 participant