View as Markdown

Merge Schedules

How the merge queue tests a pull request before its merge schedule opens, and how it keeps that pull request from holding up the ones that can merge now.


A queue rule can limit when its pull requests merge with a schedule in its merge_conditions. Outside that window, a pull request’s merge schedule is closed: it stays in the queue, and Mergify does not merge it, however green its checks are.

Mergify still runs the pull request’s checks before the window opens, so it merges as soon as the window does. It also keeps the pull request from holding up the ones whose schedule is open: they are tested first, and a pull request waiting for its window gives way to them wherever it stands in their way, even in the same batch.

queue_rules:
- name: dependencies
queue_conditions:
- author = dependabot[bot]
merge_conditions:
- check-success = ci
- or:
- label = hotfix
- schedule = Mon-Fri 09:00-11:00[Europe/Paris]
- name: default
merge_conditions:
- check-success = ci

Here, dependency updates merge on weekday mornings, unless labeled hotfix, and every other pull request merges as soon as its checks pass. The examples below use this configuration: #21 is a dependency update queued overnight, and #22, #23, #24 and #25 come from your team.

Which schedules Mergify recognizes

Section titled Which schedules Mergify recognizes

Mergify treats a merge schedule as closed only when the queue rule’s conditions prove it:

  • A schedule or current-datetime condition listed in merge_conditions, or nested in an and, closes the rule for every pull request outside its window.

  • In an or with label or author conditions, as above, it closes the rule only for the pull requests that match none of them. A dependency update labeled hotfix has its schedule open at any hour.

When a schedule sits in an or with any other condition, Mergify cannot tell in advance, and treats the pull request as if its schedule were open: it is tested in its turn, and if the window is closed when its checks pass, it waits at its place in the queue like a pull request with any other unmet merge condition. Nothing else on this page applies to it.

A scheduled freeze is not a merge schedule. A batch whose checks pass during a freeze waits for the freeze, not for its schedule, and gives up nothing described here, even if its schedule is closed too.

Testing before the window opens

Section titled Testing before the window opens

While pull requests whose schedule is open are waiting to be tested, one whose schedule is closed steps aside: it keeps its place in the queue, and they are tested first. Once none is left waiting, Mergify tests it anyway, behind every pull request that can merge now, so its checks run before its window opens. When they pass, its batch waits with the batch status waiting_schedule, and merges when the window opens.

waiting checks running 1st 2nd 3rd Order queued #21 #21 #22 #22 #23 #23 Order tested at night #22 #22 #23 #23 #21 #21 waiting checks running 1st 2nd 3rd Order queued #21 #21 #22 #22 #23 #23 Order tested at night #22 #22 #23 #23 #21 #21
#21 was queued first, but its schedule is closed: #22 and #23 are tested first, and #21 runs its checks behind them before its window opens, so it blocks neither.

Several pull requests whose schedules are closed are tested in the order their windows open, then by priority: one waiting for 08

is tested ahead of one waiting for 09
, so it is not tested on top of a pull request that cannot merge before it. A stacked pull request is ordered as if its window opened no earlier than those of the pull requests it is stacked on, since it cannot merge before them. In a few cases, such as when a priority rule lets one of them interrupt checks, priority alone orders them.

Giving up its place in the queue

Section titled Giving up its place in the queue

A batch is tested on top of the batches ahead of it, so it cannot merge before them. A batch waiting for its schedule would hold up every batch behind it until its window opens. Instead, as soon as a batch behind it can merge (its checks passed and its schedule is open), the waiting batch gives up its place. Mergify takes it, and every batch tested on top of it, back into the queue: the pull requests that can merge are tested again on the base branch and merge, and the waiting pull requests are tested again behind them.

Say #21 started its checks at 10

, inside its window, and #22 was batched behind it. The window closes at 11
, before #21’s checks end. #21 passes and waits for 09
the next morning, while #22 passes on top of it.

waiting for its schedule checks passed checks running 1st 2nd After 11:00 #21 #21 #22 #22 After #21 gives way #22 #22 #21 #21 waiting for its schedule checks passed checks running 1st 2nd After 11:00 #21 #21 #22 #22 After #21 gives way #22 #22 #21 #21
#22 passed its checks, but they ran on top of #21, which cannot merge before 09:00 the next morning. #21 gives up its place: #22 is tested again on its own and merges, and #21 runs its checks again behind it.

The waiting batch also gives way before a batch behind it can merge, in two cases:

  • A batch that can merge has not started its checks yet. A pull request whose schedule is open is batched behind a batch waiting for its schedule, or still running its checks ahead of it. The waiting batch gives way before the new batch spends a CI run on top of it. Only the waiting batches the new one would still be tested on top of are taken back: in parallel mode, one in another scope keeps its place.

  • Both schedules are closed, and the batch behind opens first. A batch waiting for 08

    behind one waiting for 09
    would be tested again at 08
    and merge a CI run late. The 09
    batch gives way while both are still closed, so the 08
    batch is tested again overnight and merges at 08
    . Mergify only does this when the rebuilt queue cannot put the 08
    batch behind a batch still closed at 08
    , or it would wait behind another batch again. Any other batch waiting for a later window that it could end up behind gives way at the same time.

A batch does not give way when every pull request of the batch behind it is stacked on one of its pull requests: rebuilt, that batch would wait behind them again.

max_parallel_checks caps how many batches run their checks at once. A batch tested ahead of its schedule only borrows a slot that no pull request needed when it started. So when every slot is taken and the next pull request waiting to be tested has its schedule open, a batch running its checks ahead of its schedule gives its check slot away: Mergify takes it back into the queue and tests the waiting pull request in its slot. It is tested again once a slot is free.

This happens whatever allow_checks_interruption is set to: that option decides what a higher priority does to running checks, and a schedule is not a priority.

checks running waiting Slot 1 Slot 2 Slot 3 Waiting Every slot taken #21 #21 #24 #24 #25 #25 #23 #23 After #21 gives its slot #23 #23 #24 #24 #25 #25 #21 #21 checks running waiting Slot 1 Slot 2 Slot 3 Waiting Every slot taken #21 #21 #24 #24 #25 #25 #23 #23 After #21 gives its slot #23 #23 #24 #24 #25 #25 #21 #21
With every slot taken, #23 can merge now but has nowhere to run its checks. #21, tested ahead of its window, gives its slot to #23 and runs its checks again once a slot is free.

When several batches could give their slot, the one the waiting pull request would be tested on top of goes first, since it would give way before that pull request’s checks start anyway. Otherwise, the batch whose window opens last gives its slot, and among those opening together, the last one in the queue.

A batch keeps its check slot when:

  • The waiting pull request’s schedule is closed too, even if its window opens sooner. The running batch frees its slot when its checks end, so taking it away would cost a CI run to gain at most one.

  • A batch behind it can merge now. Giving the slot away takes every batch behind it back into the queue too, so a batch gives its slot only when every batch behind it is waiting for its schedule as well.

  • The waiting pull request cannot merge now anyway: it is stacked on a pull request of a batch whose schedule is closed, or a scheduled freeze holds it.

  • A failed batch is being split to find the pull request that broke it. The split’s sub-batches keep their slots, and no batch ahead of them gives its own.

Only max_parallel_checks triggers this: a pull request waiting for room in a scope whose own capacity is full does not take a slot this way.

Mergify keeps a pull request whose schedule is closed out of a batch of pull requests that can merge now while the batch forms. A batch can still end up holding both. Say #21 and #26, two dependency updates, are batched together at 10

, and the window closes at 11
before their checks end. The batch waits with both for 09
the next morning. If #26 is then labeled hotfix, its schedule is open, but it shares the same batch as #21, whose schedule is not.

Mergify then splits the batch: it takes it back into the queue, #26 is tested again on its own and merges, and #21 is tested again behind it.

waiting for its schedule checks running 1st 2nd Waiting for 09:00 #21 + #26 #21 + #26 After the split #26 #26 #21 #21 waiting for its schedule checks running 1st 2nd Waiting for 09:00 #21 + #26 #21 + #26 After the split #26 #26 #21 #21
#26 was labeled hotfix after its batch began waiting for 09:00. The batch is split: #26 is tested on its own and merges, and #21 is tested again behind it.

Splitting costs both pull requests a new CI run, so Mergify only splits when the pull requests that can merge would merge sooner, and the others no later than their window allows:

  • The batch ran its checks on a batch pull request, which tells Mergify how long the new runs take.

  • The window of the pull requests that cannot merge opens after both new runs would have ended. If the window opens sooner, the unsplit batch merges sooner.

  • The pull requests that can merge still have their schedule open when their new run ends, are not stacked on a pull request whose schedule is closed, and can start their checks at once: a check slot is free for them.

  • No batch ahead or behind has failed, no batch ahead is frozen, and every batch ahead that the pull requests would still be tested on top of has its schedule open, now and when their new run ends.

What you see on the pull request

Section titled What you see on the pull request

Each of these moves interrupts the checks of the waiting pull request and of every pull request batched with it or tested on top of it. Each of them reports the schedule-blocked-ahead-yielded dequeue reason and stays in the queue: it is tested again without you doing anything.

Was this page helpful?