fix(ci): use regular merge for promotion PRs instead of squash#1201
Merged
castrojo merged 1 commit intoublue-os:mainfrom Mar 18, 2026
Merged
fix(ci): use regular merge for promotion PRs instead of squash#1201castrojo merged 1 commit intoublue-os:mainfrom
castrojo merged 1 commit intoublue-os:mainfrom
Conversation
Squash-merge permanently breaks the merge base between main and lts, causing every future promotion PR to accumulate all historical commits in its diff (19 commits in PR ublue-os#1199 when only 1 was new). The merge base stays frozen at the last shared ancestor because squash commits create orphan SHAs that Git cannot trace back to main. Regular merge (Create a merge commit) preserves the merge base by creating a commit with two parents, so future PRs only show genuinely new commits. Changes: - Simplify commit list logic: use git log lts..main directly (the tree-hash anchor workaround is no longer needed with regular merge) - Update PR body text to instruct maintainers to merge, not squash - Add explicit warning against squash-merge in AGENTS.md Assisted-by: Claude Opus 4.6 via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merged
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the main → lts promotion PR automation to avoid squash-merges (which can break the merge base and cause future promotion PRs to balloon in commits/files), and documents the correct merge strategy for maintainers.
Changes:
- Update promotion guidance to require regular merge commits (not squash-merges) for
main→ltspromotion PRs. - Simplify the promotion PR commit list generation to use
git log origin/lts..origin/mainrather than a tree-hash anchor workaround. - Expand CI/CD documentation in
AGENTS.mdwith an explicit warning against squash-merging promotion PRs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
AGENTS.md |
Documents the promotion flow and explicitly instructs maintainers to merge (not squash) promotion PRs. |
.github/workflows/create-lts-pr.yml |
Simplifies commit listing for the promotion PR and updates the PR body text to instruct maintainers to use a merge commit. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+51
to
55
| if [ -z "$LIST" ]; then | ||
| # Fallback when the commit graph can't resolve (e.g., first ever promotion). | ||
| LIST=$(git diff --name-status origin/lts origin/main) | ||
| fi | ||
|
|
|
Related Documentation 4 document(s) may need updating based on files changed in this PR: bluefin Bluefin Newsletter 2.0View Suggested Changes@@ -1,7 +1,7 @@
## Recent Completed Work
| Date Completed | Task | Overview | Impact |
|---|---|---|---|
-| 2026-03-02 | Prevent Branch Pollution with Manual LTS Promotion | A comprehensive three-layer defense was implemented to prevent branch pollution caused by merge commits and circular history. **Layer 1:** The automated Pull app (`.github/pull.yml`) was removed and replaced with a manual `.github/workflows/promote-to-lts.yml` workflow triggered via workflow_dispatch. The workflow performs a pre-flight divergence check that blocks promotion if lts has commits not in main, then performs a squash push from main to lts after validation. All changes must land in main first; direct commits to lts are prohibited. This replaced the previous PR-based approach that created merge commits and polluted the git tree. **Layer 2:** Renovate configuration updated with `baseBranchPatterns: ["main"]` to restrict automated PRs to the main branch only, preventing dependency updates on lts. **Layer 3:** Added `lts` to push triggers in 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) to enable validation builds that catch accidental merges. | Protects production lts branch from automated updates and git tree pollution. Ensures controlled, deliberate promotions from main to lts via explicit human action with squash merge strategy for linear history. Validation builds provide safety net against accidental merges without publishing. [Details](https://github.com/ublue-os/bluefin-lts/pull/1152) |
+| 2026-03-02 | Prevent Branch Pollution with Manual LTS Promotion | A comprehensive three-layer defense was implemented to prevent branch pollution caused by merge commits and circular history. **Layer 1:** The automated Pull app (`.github/pull.yml`) was removed and replaced with a manual `.github/workflows/create-lts-pr.yml` workflow that opens a draft PR from main to lts. The workflow performs a pre-flight divergence check that blocks promotion if lts has commits not in main, then opens a PR for maintainer review. Maintainers must use "Create a merge commit" (regular merge) to complete the promotion—**squash-merge must NOT be used** because it breaks the merge base between main and lts, causing future promotion PRs to accumulate all historical commits in their diffs. All changes must land in main first; direct commits to lts are prohibited. **Layer 2:** Renovate configuration updated with `baseBranchPatterns: ["main"]` to restrict automated PRs to the main branch only, preventing dependency updates on lts. **Layer 3:** Added `lts` to push triggers in 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) to enable validation builds that catch accidental merges. | Protects production lts branch from automated updates and git tree pollution. Ensures controlled, deliberate promotions from main to lts via explicit human action with regular merge strategy to preserve merge base. Validation builds provide safety net against accidental merges without publishing. [Details](https://github.com/ublue-os/bluefin-lts/pull/1152) |
| 2026-02-12 | Simplify Renovate Automerge Configuration | Renovate automerge configuration refactored from an allowlist approach to a universal pattern-based approach. All digest/pin/pinDigest updates across all managers (Dockerfile, GitHub Actions, regex/Justfile) are enabled for automerge, replacing fragmented per-container rules. GitHub Actions minor/patch version bumps also automerge. NVIDIA driver digests remain manual for compatibility review. | Reduces manual PR review for all dependency digest updates with simpler, more comprehensive automation rules. Improves security posture and reduces maintenance overhead. [Details](https://github.com/ublue-os/bluefin-lts/pull/1105) |
| 2026-01-31 | Add ibus-chewing to Bluefin LTS for zh_TW | The ibus-chewing input method is now included in Bluefin LTS, matching the default for the zh_TW locale in Bluefin Stable. This resolves issues where Chewing was configured but not available when installing from older ISOs, and improves the Traditional Chinese typing experience out of the box. | Ensures consistent and functional Traditional Chinese input for zh_TW users on both Stable and LTS. Reduces manual configuration and improves i18n parity. [Details](https://github.com/ublue-os/bluefin-lts/pull/1076) |
| 2025-12-16 | Reintroduce Renovate Automation | The Renovate dependency automation was restored. The `.github/renovate.json5` configuration was updated to include `ghcr.io/projectbluefin/common` in the automerge dependencies, ensuring that updates to this key dependency are now automatically merged. | Keeps dependencies up to date with less manual intervention, improving security and reliability. [Details](https://github.com/ublue-os/bluefin/pull/3853) |
@@ -24,7 +24,7 @@
## Summary Table
| Date | Change Summary |
|------------|---------------|
-| 2026-03-02 | Renovate restricted to main branch; Pull app replaced with squash-push promotion workflow with pre-flight divergence check; validation builds added to lts branch |
+| 2026-03-02 | Renovate restricted to main branch; Pull app replaced with PR-based promotion workflow that requires regular merge (not squash); validation builds added to lts branch |
| 2026-02-12 | Renovate automerge refactored from allowlist to universal pattern-based rules covering all digest/pin updates |
| 2026-01-31 | ibus-chewing input method added to Bluefin LTS for zh_TW locale parity |
| 2025-12-16 | Renovate automation restored for `ghcr.io/projectbluefin/common` |Bluefin OSView Suggested Changes@@ -101,16 +101,16 @@
**Promotion Process:**
-Changes flow from `main` to `lts` via the `.github/workflows/promote-to-lts.yml` workflow, which is manually triggered via `workflow_dispatch`. The workflow performs a direct squash push from main to lts after running pre-flight divergence checks. The workflow includes a divergence check that blocks promotion if the lts branch contains commits not present in main, preventing circular history and git tree pollution. This approach requires `contents: write` permission to push directly to the lts branch.
-
-All changes, including emergency CI hotfixes, must land in main first before promotion to lts—direct commits to lts are not allowed. Triggering `workflow_dispatch` serves as the human approval gate for promotion.
+Changes flow from `main` to `lts` via the `.github/workflows/create-lts-pr.yml` workflow. The workflow opens a draft PR from main to lts, which a maintainer merges using regular merge (Create a merge commit), not squash-merge. **Squash-merge must NOT be used for promotion PRs, as it breaks the merge base and causes future PRs to show all historical commits.** Regular merge preserves the merge base between branches, ensuring future promotion PRs only contain genuinely new commits.
+
+All changes, including emergency CI hotfixes, must land in main first before promotion to lts—direct commits to lts are not allowed. Merging the promotion PR serves as the human approval gate.
**Build Triggers:**
All LTS build workflows (build-dx.yml, build-dx-hwe.yml, build-gdx.yml, build-regular.yml, build-regular-hwe.yml) trigger on both `main` and `lts` branches:
- **Builds on `main`**: Triggered by pushes, pull requests, and merge groups for validation and testing
-- **Builds on `lts`**: Triggered by pushes (including automatic squash pushes from promotion) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
+- **Builds on `lts`**: Triggered by pushes (including merge commits from promotion PRs) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
**Dependency Management:**
@@ -182,7 +182,7 @@
Build workflows trigger on multiple events to support both development and production workflows:
- **main branch**: Builds triggered by push events, pull requests, and merge groups for validation and testing
-- **lts branch**: Builds triggered by push events (including automatic squash pushes from promotion) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
+- **lts branch**: Builds triggered by push events (including merge commits from promotion PRs) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
All LTS variant build workflows (build-dx.yml, build-dx-hwe.yml, build-gdx.yml, build-regular.yml, build-regular-hwe.yml) trigger on both branches to ensure changes are validated before promotion to production.
Kernel Version ManagementView Suggested Changes@@ -168,13 +168,13 @@
**Promotion Workflow:**
-[The promote-to-lts.yml workflow performs a squash push from main → lts with pre-flight divergence checks](https://github.com/ublue-os/bluefin-lts/pull/1177). The workflow:
-
-1. Runs a pre-flight check that blocks promotion if lts has diverged from main
-2. Performs a `git merge --squash origin/main` to collapse all pending changes into a single staged changeset
-3. Pushes one clean commit to lts per promotion event, maintaining linear history
-
-[The workflow requires `contents: write` permissions](https://github.com/ublue-os/bluefin-lts/pull/1177) and uses `workflow_dispatch` as the human approval gate—a maintainer must explicitly trigger promotion. **Never commit directly to `lts`**: all changes including CI hotfixes must land in main first, preventing circular history and git tree pollution.
+[The create-lts-pr.yml workflow opens a draft PR from main → lts when content differs](https://github.com/ublue-os/bluefin-lts/pull/1201). The workflow:
+
+1. Checks for content differences between `main` and `lts` branches
+2. Creates or updates a draft PR showing commits pending promotion
+3. A maintainer reviews and merges the PR using **"Create a merge commit"** (not squash-merge)
+
+**Critical:** Maintainers must use regular merge (Create a merge commit), NOT squash-merge. Squash-merge creates orphan commits that permanently break the merge base between `main` and `lts`, causing every future promotion PR to accumulate all historical commits in its diff and bloat indefinitely. Regular merge preserves the merge base and keeps future PRs clean. **Never commit directly to `lts`**: all changes including CI hotfixes must land in main first, preventing circular history and git tree pollution.
**Scheduling Architecture:**
@@ -302,7 +302,7 @@
| [build_files/post/dual-sign.sh](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/build_files/post/dual-sign.sh) | Module dual-signature implementation | ublue-os/akmods |
| [build_files/prep/dual-sign-check.sh](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/build_files/prep/dual-sign-check.sh) | Module signature verification | ublue-os/akmods |
| [Containerfile.in](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/Containerfile.in) | Multi-stage akmods build container | ublue-os/akmods |
-| [.github/workflows/promote-to-lts.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/promote-to-lts.yml) | Performs squash push from main → lts with pre-flight divergence checks | ublue-os/bluefin-lts |
+| [.github/workflows/create-lts-pr.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/create-lts-pr.yml) | Opens draft PR from main → lts; maintainer merges using regular merge (not squash) | ublue-os/bluefin-lts |
| [.github/workflows/scheduled-lts-release.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/scheduled-lts-release.yml) | Weekly LTS release dispatcher | ublue-os/bluefin-lts |
## Related TopicsUniversal Blue Build and Update SystemView Suggested Changes@@ -63,13 +63,11 @@
The system implements a three-layer defense strategy to protect the `lts` production branch from accidental pollution:
-**Layer 1: Manual Promotion Workflow** - A manual `promote-to-lts.yml` workflow enables explicit promotion of tested changes from `main` to `lts`. The workflow:
-- Requires manual trigger via GitHub Actions UI (`workflow_dispatch`)
-- Performs a direct squash push from `main` to `lts` (no intermediate branches or PRs)
-- Includes a pre-flight divergence check that blocks promotion if `lts` has commits not reachable from `main`
-- Automatically performs the squash push after pre-flight checks pass
-- Requires `contents: write` permission for direct push to `lts` branch
-- Replaces the previous automatic Pull app (`.github/pull.yml` has been deleted)
+**Layer 1: Manual Promotion Workflow** - The `create-lts-pr.yml` workflow automatically opens a draft PR from `main` → `lts` when content differs. A maintainer reviews and merges the PR (Create a merge commit, not squash) as the approval gate. The workflow:
+- Automatically creates/updates a draft PR when `main` and `lts` content differs
+- Requires manual merge via GitHub PR UI (must use "Create a merge commit")
+- The merge triggers `push` events on `lts` that run validation builds (`publish=false`)
+- **CRITICAL**: NEVER squash-merge promotion PRs — squash-merge creates orphan commits that permanently break the merge base between `main` and `lts`, causing every future promotion PR to accumulate all historical commits in its diff. Regular merge preserves the merge base and keeps future PRs clean.
- **CRITICAL RULE**: NEVER commit directly to `lts` — all changes must land in `main` first
**Layer 2: Renovate Restriction** - Renovate configuration restricts dependency updates to target only the `main` branch via `baseBranchPatterns: ["main"]`. This prevents automated dependency PRs from targeting the `lts` branch, ensuring the production branch receives updates only through manual promotion.
@@ -387,7 +385,7 @@
| File Path | Description | URL |
|-----------|-------------|-----|
| `.github/workflows/scheduled-lts-release.yml` | Dispatcher workflow for weekly LTS releases | [View](https://github.com/ublue-os/bluefin/blob/main/.github/workflows/scheduled-lts-release.yml) |
-| `.github/workflows/promote-to-lts.yml` | Manual promotion workflow from main to lts | [View](https://github.com/ublue-os/bluefin/blob/main/.github/workflows/promote-to-lts.yml) |
+| `.github/workflows/create-lts-pr.yml` | Automatic PR creation workflow for main → lts promotions | [View](https://github.com/ublue-os/bluefin/blob/main/.github/workflows/create-lts-pr.yml) |
| `.github/renovate.json5` | Renovate configuration with branch restrictions | [View](https://github.com/ublue-os/bluefin/blob/main/.github/renovate.json5) |
| `.github/workflows/reusable-build.yml` | Reusable CI/CD workflow (ublue-os/main) | [View](https://github.com/ublue-os/main/blob/5ef6bb2adf95dd36b4d428e643a88ad510b7b988/.github/workflows/reusable-build.yml) |
| `.github/workflows/reusable-build.yml` | Reusable CI/CD with rechunking (Bluefin) | [View](https://github.com/ublue-os/bluefin/blob/3f18fcfb4b16d8ae005cef071395c0132672ebce/.github/workflows/reusable-build.yml) |Note: You must be authenticated to accept/decline updates. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PR #1199 showed 19 commits and 6 changed files when only 1 commit (d151470) was genuinely new. The PR was unmergeable (
mergeable_state: dirty).Root cause: Squash-merge of PR #1196 created an orphan commit on
ltsthat Git cannot trace back to anymaincommit. This permanently froze the merge base atff85922, causing GitHub to compute the PR diff from that ancient point — including all historical commits in every future promotion PR.This problem compounds over time — each squash-merge adds another orphan, and the commit/diff bloat grows without bound.
Solution
Switch promotion PRs from squash-merge to regular merge (Create a merge commit). Regular merge creates a commit with two parents, advancing the merge base so future PRs only contain genuinely new commits.
Verified locally:
Changes
create-lts-pr.yml: Simplified commit list logic — usegit log lts..maindirectly (the tree-hash anchor workaround is no longer needed). Updated PR body to instruct maintainers to merge, not squash.AGENTS.md: Documented that promotion PRs must use regular merge, with explicit warning against squash-merge.Pre-requisite (already done)
The merge base was repaired by merging
mainintoltswith a regular merge commit (d5a0149). Current state: merge base =d151470, zero content diff, identical trees. PR #1199 should be closed.Assisted-by: Claude Opus 4.6 via GitHub Copilot