How to run a merge queue during a code freeze
You run a merge queue during a code freeze by blocking the merge and leaving everything else alone. Schedule a freeze in Mergify, exclude your hotfix label, and pull requests keep queuing and testing while nothing lands on the frozen branch.
In one paragraph
Create a scheduled freeze with a start, an end, and a timezone, scope it to the branch you are stabilizing, and exclude the label your team puts on hotfixes. Reviews and CI carry on, and the queue keeps testing pull requests against the state the branch will have at merge time. When you delete the freeze, the backlog drains in order, already tested. Nobody has to re-queue anything.
What should a code freeze actually block?
Most code freezes are enforced by asking people to stop. Someone posts in Slack, someone else merges anyway on Thursday afternoon, and the release engineer finds out on Friday. The freeze that works is the one your tooling enforces, and the only thing it needs to enforce is that code stops reaching the release branch.
Everything upstream of the merge can keep running. People open pull requests, reviewers review, CI runs, and the queue tests changes against the branch state they will land on. That work is not what puts your release at risk, and stopping it just means the backlog arrives untested the moment the freeze lifts.
So before you configure anything, write down two things: which branch is frozen, and what gets through anyway. In practice the second one is always some version of "a hotfix with an approval". Everything below is how you encode those two answers.
Freeze, pause, or schedule: which one do you need?
Mergify has four ways to stop something from merging, and they are not interchangeable. Picking the wrong one is how you end up cancelling CI you wanted to keep, or freezing a repository when you meant to hold a single pull request.
| Tool | What it blocks | CI keeps running | Set up with | Use it for |
|---|---|---|---|---|
| Scheduled freeze | Merging, for every pull request matching the freeze conditions | Yes | Dashboard, or mergify freeze create | A dated freeze: release week, holidays, an audit window |
| schedule in merge_conditions | Merging, outside the window you declare | Yes | .mergify.yml | A recurring window: no merges at night or on weekends |
| Queue pause | Merging and queue CI, and running queue checks are cancelled | No | Dashboard, or mergify queue pause | A CI outage, where testing anything is pointless |
| Merge-After header | One pull request, until a timestamp you set | Yes | The pull request description | A single change that is ready but must not ship before a date |
For a calendar code freeze, the scheduled freeze is the one you want. It is the only one that takes both a start and an end date, so it does not depend on the person who set it up remembering to switch it off.
How do you schedule the freeze?
From the Mergify dashboard, open Merge Protections, then Freezes, and click Schedule a Freeze. You give it a reason, a start, an end, and the conditions that decide which pull requests it covers.
The same thing from a terminal, which is what you want if the freeze is part of a release runbook:
mergify freeze create \
--reason "Release 4.2 stabilization" \
--timezone "Europe/Paris" \
--start "2026-12-18T18:00:00" \
--end "2027-01-05T09:00:00" \
-c "base=main" \
-e "label=hotfix"The -c conditions select what the freeze covers, and -e excludes what escapes it. Without -c the freeze applies to every pull request in the repository, which is fine for an incident and usually too broad for a release.
Leave out --end and you get an emergency freeze with no expiry, which is the right shape during an incident. Remember to delete it afterwards, because an open-ended freeze nobody remembers is worse than no freeze at all. mergify freeze list shows what is active.
How do hotfixes get through?
A freeze with no exception gets bypassed by hand the first time production breaks, and once someone has merged around the freeze the freeze is over. Give the exception a name instead: the -e "label=hotfix" exclusion above means a pull request labelled hotfix is not covered by the freeze at all.
Excluded does not mean unchecked. The pull request still goes through the queue, still needs its approvals, and is still tested against the current state of the branch before it merges. You have made the emergency path visible and auditable rather than removing it.
Route those hotfixes through their own queue so they do not sit behind the frozen backlog. This config gives hotfixes an unrestricted queue and holds everything else to working hours:
queue_rules:
# Hotfixes merge whenever they are green, freeze or not.
- name: hotfix
queue_conditions:
- label = hotfix
merge_conditions:
- check-success = ci
merge_method: squash
# Everything else queues and gets tested all week,
# but only lands during working hours.
- name: default
merge_conditions:
- check-success = ci
- schedule = Mon-Fri 09:00-17:00[Europe/Paris]
batch_size: 5
merge_method: squashThe schedule condition on the default queue is a standing freeze in config form. Pull requests still enter the queue at 2am and still get tested, they just wait at the front until the window opens. It covers recurring windows, so use it for weekends and out-of-hours, and use a scheduled freeze for anything tied to calendar dates.
Replace check-success = ci with the name your own CI reports before you commit this. The queue rules reference covers the rest of the keys.
What happens to the queue while merges are blocked?
It keeps working. Pull requests enter the queue, Mergify builds the branch state each one would produce, and your CI runs against it. The only step that does not happen is the merge itself, so the queue accumulates changes that are already known to be good.
This is the difference between a freeze and a pause. Pausing the queue stops the scheduling too and cancels the checks it can cancel, which is what you want during a CI outage and exactly what you do not want during a release freeze. If your CI is healthy and you are just holding the door shut, freeze rather than pause.
One pull request that needs to wait for a date, rather than a whole branch, does not need a freeze at all. Add a header to its description and Mergify holds it:
Merge-After: 2026-01-05T09:00:00[Europe/Paris]How do you lift a freeze without a stampede?
Delete the freeze and the queue starts merging again, in the order pull requests joined it:
mergify freeze list
mergify freeze delete <freeze_id> --reason "Release 4.2 shipped"A freeze that lasted two weeks leaves a backlog, and merging it one pull request at a time means one full CI run each. Batching tests several together and bisects the batch when it fails, which is the difference between draining a 40 pull request backlog in an afternoon or in two days. Raise batch_size before you lift the freeze, not after.
The other thing worth doing before you unfreeze is checking the queue for changes that have gone stale. Anything opened before the freeze started has had two weeks for the world to move under it. The queue will catch that on its own, since it tests against the current branch state rather than the state the author saw, but a failing batch is easier to read when you were expecting it.
Where this fits
Freezes are one of the controls of the Mergify merge queue, alongside rules on labels, files, authors, and pull request dependencies. They work on top of the queue itself, which is what makes the "keep testing while blocked" behaviour possible in the first place.
If you do not have a queue yet, start with setting one up on GitHub, then come back and add the freeze. For the mechanics of what a queue does to your branch, the merge queue guide is the longer explanation.
FAQ
Should people stop opening pull requests during a code freeze?
No. That is the mistake most freezes make. Reviews, CI, and queueing can all keep running while merging is blocked. The freeze only needs to stop code from reaching your release branch.
What is the difference between freezing merges and pausing the queue?
A freeze blocks merges while your checks keep running, so the queue stays warm. Pausing stops the queue from scheduling or merging anything and cancels the checks it can cancel. Use a freeze for release and calendar windows, and pause for a CI incident where running tests would only produce noise.
Can we freeze one branch and leave the others open?
Yes. A scheduled freeze takes matching conditions, so a freeze created with base=main leaves every other branch merging normally. The same works for a freeze scoped to a label, an author, or a file path.
How do hotfixes get through the freeze?
Add an exclusion when you create the freeze, for example an exclusion on label=hotfix. Pull requests carrying that label are not covered by the freeze and merge through the queue as usual, tested against the real state of the branch.
What happens to pull requests that were already in the queue when the freeze started?
They stay in the queue in the same order. Mergify keeps testing them and holds them at the front instead of merging. When you lift the freeze, they merge in order without anyone re-queueing them.
Can we set up a recurring freeze without touching the dashboard?
Yes. A schedule condition in merge_conditions covers recurring windows such as weekends or out-of-hours, and it lives in .mergify.yml alongside the rest of your config. Scheduled freezes cover one-off calendar windows, which a recurring schedule cannot express.
Last updated: