Skip to content

ci: Mergify update rule reports a permanent red check on any PR that is behind and conflicting #3574

Description

@eleshar

Problem

The Mergify rule added in #3563 — "Keep same-repository pull requests on develop current" (update: {}) — reports a red "Base branch update has failed" on any pull request that has fallen behind develop and conflicts with it.

update merges the base branch in. It cannot resolve a conflict; that is inherent to the action, not a misconfiguration. The rule already excludes conflicting PRs with -conflict, but the exclusion is evaluated when Mergify assesses the rule, and a conflict created by a develop landing in between still reaches the action.

Evidence

Observed 2026-09-25 on #3487 immediately after #3571 merged to develop:

Rule: Keep same-repository pull requests on develop current (update) — Base branch update has failed

Reproduced locally — the update is a plain merge, and it conflicts:

$ git merge origin/develop   # into fix/jest-ci-3479
Auto-merging CHANGELOG.md
CONFLICT (content): Merge conflict in CHANGELOG.md

Both sides had added an entry to the same [Unreleased] list. Mergify's documented failure text for this case is "merge conflict between base and head".

The rule was added in c396510544 (#3563) and had no merge to develop after it landed until #3571, so this was its first real execution. It fired and failed immediately.

Why it is not transient

This repo makes CHANGELOG.md a structural conflict point. The Require changelog or skip label gate demands a [Unreleased] entry on essentially every pull request, and every entry lands in the same short list. So while two pull requests are open, their changelog edits overlap by default, and whichever lands second guarantees a conflict for the other.

That makes "PR is behind and conflicts" the common case rather than the exception, so this rule will keep producing a red check on most stale pull requests. A human still has to merge develop in — which is what update would have done for them.

Impact

Low, and cosmetic. The active develop-branch-ruleset requires only three status checks:

  • Route PR template and apply labels
  • Validate changelog on PR
  • actionlint

The Mergify check is not among them, so it never blocks a merge. The cost is a red X that trains reviewers to ignore a check, plus noise on every stale PR.

Options

Pick one; the first is the smallest.

  • Drop -conflict from the conditions and accept the red check, documenting that a stale-and-conflicting PR needs a manual merge. Cheapest, honest, and stops pretending the rule covers cases it cannot.
  • Narrow the rule so it never meets a conflict. The changelog gate means the conflict is usually only CHANGELOG.md. Mergify has no "update only if mergeable" condition, but the rule could be paired with a check that the PR is mergeable, or restricted to PRs that do not touch CHANGELOG.md.
  • Resolve the changelog structurally so overlapping [Unreleased] entries stop colliding — for example a per-entry anchor or generating the section from merged PR metadata. This removes the root cause and helps the changelog gate generally, but is a much larger change and should be scoped on its own.
  • Delete the rule. Once a merge queue or a strict required-check policy is in place the base does not need to be merged into the branch. fix(ci): Dependabot and Mergify configuration is stale and dependency PRs cannot auto-merge #3476 and ci(mergify): keep stale pull requests current with develop #3563 already discuss Mergify's role; worth revisiting there rather than here.

Acceptance

  • Merging develop into a non-draft, non-fork pull request requires no manual step when the merge is clean
  • A pull request that genuinely conflicts reports why, and does not present a permanently red check as if the automation were still working
  • The chosen option is recorded in .github/mergify.yml comments, including the interaction with the changelog gate

Notes

Not a regression: the rule is new (#3563) and this is its first execution. #3487 needed a manual develop merge for the same reason and has since merged.

Relates to #3476, #3563, #1076.

Activity

  1. linear-code commented on Sep 25, 2026

    @linear-code
  2. eleshar commented on Sep 26, 2026

    @eleshar
    ContributorAuthor

    Correction after implementing this

    Partially resolved by #3581, and one part of the original diagnosis turned out to be wrong. Recording both so this issue is not closed as "fixed" when the red check is still there.

    What #3581 does

    .gitattributes now marks CHANGELOG.md merge=union, so the two-pull-requests-collide-on-the-same-[Unreleased]-lines problem no longer produces a conflict at all.

    Verified on real data, not a fixture:

    So the manual step this issue asks for drops from resolving a conflict by hand to merging and pushing.

    #3581 also fixes two real defects found while doing it, both verified:

    • The changelog validator's CHK_UNIQUE_CONTENT skipped any entry whose content equalled the entry under validation — which is exactly what an exact duplicate is. Duplicating a real entry produced 0 flags before the fix and 2 after. Union-merging makes an exact duplicate the most likely way to produce one, so this had to land in the same change.
    • That validation library had no unit tests at all, which is how the skipped comparison survived. Five were added.

    What #3581 does not do

    It does not stop the red check. I claimed it would, in the PR description, and that was wrong.

    When #3581 found itself behind and conflicting, the rule check went red. Asking Mergify to retry gave a precise answer:

    ☑️ Nothing to do, the required conditions are not met
    - [ ] -conflict  [📌 update requirement]
    - [X] #commits-behind > 0  [📌 update requirement]
    

    Mergify refuses before attempting anything, using the GitHub API's mergeability. That is standard server-side merge semantics and does not consult .gitattributes, so the API reported CONFLICTING, Mergify declined, and the union driver never ran.

    I should have tested whether Mergify honours .gitattributes before asserting it. Its documentation says the base is merged in without stating the mechanism, and I treated that ambiguity as favourably ambiguous. A @Mergifyio update comment answers it in a minute.

    Revised scope of this issue

    ✅ Spurious CHANGELOG.md conflicts Resolved by #3581
    ❌ Red check on a conflicting pull request Still open
    Informational The red check is cosmetic — the ruleset requires only actionlint, Validate changelog on PR and Route PR template, so it never blocks a merge

    The remaining question is whether the red check is worth eliminating at all. It cannot be fixed from configuration: Mergify applies the no-conflict requirement itself, before the action runs. The realistic options are:

    1. Accept it. It is cosmetic, it clears as soon as the conflict is resolved, and fix: ci - union-merge the changelog so a merged pull request is not stuck behind develop #3581 has made resolving it trivial. Cost: a recurring red X that trains reviewers to ignore a check.
    2. Delete the rule. Now that fix: ci - union-merge the changelog so a merged pull request is not stuck behind develop #3581 removes the conflict that caused it in practice, the rule fires far less often, and the cases it cannot handle are the only ones where it misleads. It would still be doing useful work on behind-but-mergeable pull requests, which genuinely cannot merge without it — chore: opencode - Wire graft and graphify code graphs (#3569) #3567 is the example.
    3. Gate it on approval, so it only updates a pull request once a human has reviewed it. This is the pattern the cloudposse/atmos project adopted for the same symptom. It does not remove the red check, but it stops the rule from touching a pull request at the moment it is most likely to conflict.

    Option 2 deserves a real answer before choosing, because the rule's value has changed since #3563 wrote it: its original comment says a stale pull request is "blocked from merging exactly as much as one twenty behind", which was true under a strict required-check policy and still is. So it should not be deleted casually.

    No action is needed from a maintainer to land #3581. This issue stays open for the red check alone.

  3. eleshar commented on Sep 26, 2026

    @eleshar
    ContributorAuthor

    Reopening: #3581 auto-closed this as completed, and that overstates what it fixed.

    Fixes #3574 in the pull request body was my error. #3581 removed the friction — the hand-resolved CHANGELOG.md conflict — but not the red check this issue is titled after.

    Measured after the merge (4112a8159d, with merge=union on develop):

    #3495  CONFLICTING/DIRTY      #3551  CONFLICTING/DIRTY
    #3525  CONFLICTING/DIRTY      #3550  CONFLICTING/DIRTY
    #3524  UNKNOWN/UNKNOWN        #3546  CONFLICTING/DIRTY
    #3558  UNKNOWN/UNKNOWN        #3387  CONFLICTING/DIRTY
    

    Still conflicting, while a local git merge origin/develop in the same tree succeeds cleanly for every one of them:

    #3495  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    #3525  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    #3551  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    #3550  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    #3546  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    #3387  0 conflicts  Merge made by the 'ort' strategy  entries 388 -> 389
    

    That settles the caveat, and it is negative

    GitHub's server-side mergeability does not consult .gitattributes merge drivers. The attribute is now on develop and the API still calls these pull requests conflicting. So:

    Why this stays open

    The red check is unfixable from this repository's configuration, because Mergify applies the no-conflict requirement itself before the action runs, and the signal it uses ignores repository attributes. The options from the earlier comment still stand:

    1. Accept it. Cosmetic — the ruleset requires only actionlint, Validate changelog on PR and Route PR template, so it never blocks a merge.
    2. Remove auto_merge_conditions. Independently tracked in fix(ci): Dependabot and Mergify configuration is stale and dependency PRs cannot auto-merge #3476, where that setting is now provably inert and producing a permanently failing Auto-queue protection.
    3. Decide the owner question in fix(ci): Dependabot and Mergify configuration is stale and dependency PRs cannot auto-merge #3476 — whether Dependabot PRs may merge without a human code-owner review. That is the real reason bot PRs queue forever, and it is not a configuration question.

    Leaving this open rather than closing it, because the title of this issue is still true.

  4. eleshar commented on Sep 30, 2026

    @eleshar
    ContributorAuthor

    Still live on develop; the fix exists but is a draft one commit behind

    Re-verified against develop @ 1a1021eea8.

    The rule is still there. .github/mergify.yml:76-84:

      - name: Keep same-repository pull requests on develop current
        conditions:
          - base=develop
          - -draft
          - -from-fork
          - -conflict
          - '#commits-behind > 0'
        actions:
          update: {}

    It fired again 20 minutes ago. The develop push that merged #3524 (1a1021eea8, 14:35Z) produced merge commits on 11 open PR heads between 14:36:04Z and 14:38:13Z, each with the subject Merge branch 'develop' into <branch>. That is the update action doing the merge. All 15 open PR heads now have a merge base with develop and merge cleanly, so the conflict symptom has not recurred since the #3581 union-merge landed.

    The fix is open PR #3693 (draft, 003a7c615, one commit behind develop, author @eleshar). It removes the rule from .github/mergify.yml and replaces the behaviour with .github/workflows/keep-pr-current.yml, which calls PUT /repos/{owner}/{repo}/pulls/{n}/update-branch and reports a conflict as a comment with a successful run. It has 0 unresolved review threads, and a CodeRabbit CHANGES_REQUESTED on 00f63199b which is an ancestor of the head, so that verdict is still live.

    Status: real, with a known fix that needs a decision. What it needs is not code work — it is (a) the live CodeRabbit verdict cleared, and (b) #3693 taken out of draft. Closing is a maintainer call.

    Related: #3476 remains open on the Dependabot/Mergify auto-merge half, which is a different defect.

  5. eleshar commented on Sep 30, 2026

    @eleshar
    ContributorAuthor

    One correction to my earlier comment: the "Fixes" claim on #3581 is stale, and this issue should stay open

    #3581 merged on 2026-09-26 with Fixes #3574 in its body. It did not fix this, and it should not have claimed to:

    What #3581 actually changed is the CHANGELOG.md merge attribute, so the cause of most behind-and-conflicting states went away. What it did not do is remove the Mergify update rule, and that rule is the thing this issue is about. .github/mergify.yml:76-84 is still on develop, and it fired on 11 pull requests at 14:36–14:38 today.

    So the outcome is the one that matters for triage: this issue is correctly open, and #3581's body carries a closing keyword that its content does not support. The other two cases I checked do not have this problem — #3537's is the mirror image, where the pull request genuinely did the work but was merged into a stack branch that never reached develop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions