All posts

code-freeze

How to Run a Holiday Code Freeze Without Blocking Your Team

A holiday code freeze protects production during year-end coverage gaps. Here is how to scope it, schedule it, handle emergencies, and lift it cleanly.

Cody Flynn··8 min read

A holiday code freeze is a planned restriction on merges and deployments during the year-end period, typically the two to three weeks spanning Christmas and New Year, when engineering coverage is thin and on-call response time is at its worst. Done well, it is one of the highest-value controls you can run: it removes the most common cause of production incidents at exactly the window when fixing them is hardest. Done poorly, it is a six-week engineering halt that produces resentment, a backlog of held changes, and a chaotic surge of deploys the moment it lifts.

The difference is scope, scheduling, and enforcement. This guide covers each in order.

Why does a holiday freeze make sense?

The case for a holiday code freeze is not that your engineers are careless. It is that incident response depends on people, and people are unavailable. During a normal week, a bad deploy at 2pm reaches an on-call engineer in minutes, who can roll back within the hour. During the week between Christmas and New Year, that same deploy might not get a human response for eight hours, long after a small problem has compounded.

Research on software delivery performance consistently shows that change failure rate, not deployment frequency, is what separates high performers from low performers. A holiday freeze does not improve your change failure rate. What it does is remove the opportunity for a failure during the specific window where your ability to respond is worst. That is a narrow but legitimate use of a freeze.

The evidence on when code freezes work comes back to the same pattern: peak risk plus reduced capacity. A December freeze fits both criteria.

When should the freeze start and end?

A concrete window is better than a vague one. Most teams default to too long, starting a week before anything is actually at risk and extending a week after coverage returns, because it feels safer. It is not safer, it is just more disruptive.

A reasonable window for most engineering orgs:

  • Start: The last business day before the holiday week begins. For Christmas-centered schedules that is usually December 23 or 24. Most teams with a December 25 holiday are already operating at reduced capacity the week of the 18th, so evaluate your actual support schedule rather than defaulting to a fixed date.
  • End: The first business day when full on-call coverage resumes. If you have a formal on-call schedule, tie the freeze end to the return of normal rotation rather than to a calendar date. January 2 is common, but if your team is split across time zones and the last region comes back January 5, end there.

Define these dates explicitly in writing before the freeze starts and share them with the whole engineering org. "Until further notice" is not a freeze end date.

How to scope the freeze without blocking everyone

The most common mistake is freezing everything when only some systems are at elevated risk.

A service that receives one request per day and has two engineers on it does not need the same freeze rules as your billing service or your customer-facing checkout. Scoping the freeze to the repos and environments that actually matter lets engineers on low-risk systems keep working through the holidays without feeling penalized.

When deciding scope, ask:

  1. Which systems, if they fail, would cause significant customer impact or revenue loss?
  2. Which systems have the thinnest on-call coverage during this window?
  3. Which systems had incidents in the last 90 days?

The answer to those three questions gives you a freeze scope. Anything outside that list can probably stay open, or move to a softer protocol (merge allowed, no production deploy).

A scoped holiday freeze might look like this:

ScopeRule
org/billing-* and org/checkout-*Block merges and deployments to production
org/api-gatewayBlock deployments to production only; merges allowed
org/internal-*No freeze; normal process applies

The freeze rules and glob patterns in NoShip let you express this exactly: per-repo patterns, per-environment patterns, and separate block types for merges vs. deployments, so you do not have to treat your entire GitHub org as a single unit.

Scheduled vs. manual activation

If your holiday freeze happens every year on roughly the same dates, set it up as a recurring scheduled freeze rather than declaring it manually each year. Manual activation has two failure modes: someone forgets to activate it, or someone forgets to end it.

An RRULE-based recurring schedule activates the freeze automatically at the right time, without requiring anyone to remember, log in, or send a Slack message at 5pm on December 23rd. It also ends automatically, which is equally important. The freeze lifting without a calendar reminder from a release manager means engineers are not sitting idle at 9am on January 2nd waiting for someone to click a button.

Set the freeze up once in November, verify the activation and deactivation times are correct, and then stop thinking about it until January.

The emergency override path

Production will have a real problem during some holiday freezes. The freeze needs an override path, and you need to define it before the freeze starts, not the moment someone's paging you at 11pm on December 26th.

A workable override process:

  1. The engineer who needs the fix submits an override request with a brief explanation of the customer impact.
  2. A second approver (on-call lead, release manager, or engineering manager) reviews and approves.
  3. The merge or deployment proceeds, and the action and approver are logged automatically.
  4. The rest of the freeze stays active.

The key design property is that the override requires two people but does not require an admin to manually disable the freeze for everyone. A dual-approval workflow keeps the bar high enough that low-priority changes do not slip through, while still letting a genuine incident fix clear in under 30 minutes.

If your override process is "find someone with GitHub admin access to disable the branch protection rule," the barrier is too high and too informal. You will either get a dangerous workaround at 2am or a two-hour delay on a critical fix. See how to block merges to main on GitHub for what enforced freeze controls look like at the GitHub layer.

Communicating the freeze to your team

A freeze announcement should land at least two weeks before the freeze starts. Engineers who know a freeze is coming can front-load their work, avoid planning risky migrations for December 22nd, and set up personal pipelines so the freeze does not catch them mid-deploy.

The announcement should cover:

  • The exact start and end dates, in the team's local time zone or in UTC with a converter
  • Which repos and environments are frozen (link to the freeze rule list, not just a summary)
  • What is not frozen
  • How to request an override, and who approves it
  • Where the audit log lives if anyone needs to check it

Mention in the announcement that the freeze is enforced in tooling, not just declared in Slack. Engineers who have lived through soft freezes will appreciate knowing that the enforcement is automatic and that a tired Friday-afternoon deploy will not slip through.

Planning the thaw

The end of a holiday freeze is its own risk. Every engineer with held-back work wants to ship the moment the freeze lifts, and they will, unless you plan for it.

Before the freeze ends:

  • Review the list of changes waiting to go out. Anything that has been sitting for two weeks has probably diverged from main and may have hidden merge conflicts.
  • Sequence deploys by risk. Ship the lower-risk, smaller changes first while the team is fresh and watching dashboards. Save the big migrations for day two or three, after the pipeline has been warm for a bit.
  • Assign someone to watch the deployment dashboards for the first few hours after the freeze lifts.
  • Avoid scheduling the freeze end right before a Friday or a long weekend.

A structured thaw takes 30 minutes of planning and prevents a post-freeze incident surge that would take days to recover from.

Soft freeze vs. enforced freeze: what the difference looks like

Soft freezeEnforced freeze
ActivationSlack message or emailAutomated status check
ComplianceDepends on everyone reading and rememberingAutomatic, no memory required
Override pathFind an admin to reverse the ruleStructured approval workflow
Audit trailWhatever is in SlackTimestamped log of every block and override
Failure modeForgotten by one engineer at 6pmRequires explicit bypass or override

A soft freeze is a social contract. An enforced freeze is a control. They are not the same thing, and the difference matters most at the exact moment you care about it most: the tired deploy on December 23rd that no one was thinking about.

How NoShip handles holiday freeze scheduling

NoShip is a GitHub App built for this pattern. You schedule a freeze window with a start date, end date, and RRULE for annual recurrence. Rules scope the freeze to specific repos, branches, and environments via glob patterns, so the billing service and checkout are frozen while internal tooling stays open. Emergency overrides go through a dual-approval workflow and land in an audit trail automatically. NoShip never requests access to your source code.

The freeze windows and scheduling concepts cover the full mechanics. If you are replacing a manual December process you run every year, a recurring scheduled freeze is a direct swap with no ongoing maintenance required.

A holiday freeze that runs automatically, scopes correctly, and has a real override path is invisible to most of your team. That is the goal. The best freezes are the ones nobody notices because they just worked.

Keep reading