← Back to Branch Protection Drift Check

Why branch protection drifts silently

Most configuration in a software project lives in a file: a linter's rule set, a CI workflow, an environment's variable list. All of it goes through version control, so a change to it is a diff someone reviews before it takes effect, and a change nobody meant to make is visible in git blame forever.

GitHub branch protection is not that. It lives in repository settings — a live control panel a maintainer edits directly, with no commit, no pull request, and no reviewer. The audit log records that a setting changed and who changed it, but nothing compares the new state to what the team actually agreed the policy should be. There is no diff to notice, because there was never a file to diff.

The ordinary way it happens

It is rarely a decision to weaken security. It is an incident: production is down, the fix is ready, and required reviews or a status check are standing between the fix and the deploy. Someone with admin access turns the rule off, ships the fix, and means to turn it back on once things calm down. The incident ends; the follow-up task to re-enable the rule does not get the same urgency the incident itself had, and it quietly falls off the list.

Months later, a different change — one that genuinely needed review — merges without it, because the rule that would have required a review has been off the whole time. Nobody decided that outcome. Nobody would have agreed to it if asked directly. It happened because the gap between the setting changing and the setting mattering can be arbitrarily long, and nothing in GitHub's own UI narrows that gap by reminding anyone what is currently off.

Why "read the settings page" doesn't fix it

The settings page shows the current state accurately. What it cannot show is whether the current state matches what your team decided the policy should be, because GitHub has no concept of your declared policy — only of whatever the toggles currently say. Reading the page tells you the branch is unprotected in some way; it does not tell you that unprotected way is a deviation from anything, unless you already remember, precisely, what every toggle used to be.

That is the gap a declared policy closes: write down, once, what every rule is supposed to be — required reviewer count, admin enforcement, force-push and deletion rules, required status checks — and compare the live settings against that written statement whenever you have reason to check, rather than against memory.

What to actually do about it

  1. Write the policy down once, in whatever form your team will actually maintain — a paragraph in a wiki, a YAML file in the repo, or just the plain-text field list this tool accepts.
  2. After any incident that involved touching branch protection, check the live settings against that policy as part of the incident's own follow-up — not as a separate task that competes for priority once the pressure is off.
  3. Run the comparison periodically regardless — in CI on a schedule, or by hand before a compliance review — because an incident is not the only way a setting can be that no diff would ever have surfaced.

Compare your own repository's actual settings against a declared policy →