GitHub scheduled the migration of all legacy tag protection rules to tag rulesets for August 30, 2024, after first removing the ability to add new protection rules through the GitHub.com UI. The dangerous part is not finding the replacement screen; it is discovering that two sensible-looking rulesets can combine into a policy nobody explicitly designed.
Protected tags often sit at the boundary between “a commit passed CI” and “this artifact is a release.” A misplaced v1.8.3 tag can send a package, container, or deployment workflow down the wrong path; deleting it later may not undo a published artifact. Treat the move to rulesets as a release-control review, not as a one-for-one administrative conversion.
1. Start with the migration reality, not the old UI
GitHub’s sunset notice described a staged retirement: repositories without tag protection rules could no longer add them through the GitHub.com UI beginning May 30, API brownouts followed from July 24 through August 14, and GitHub planned migration of all tag protection rules to a new tag ruleset on August 30, 2024. In other words, a team maintaining a legacy pattern today should plan around rulesets, not around recovering an old protected-tags workflow.
The useful distinction is that legacy tag protection expressed a narrow intent: protect tags matching a pattern. A repository ruleset is broader policy machinery. It has a target, one or more rules, and a way to handle actors allowed to bypass the rules. That broader model is valuable, but it means copying the pattern alone is incomplete.
Before changing anything, answer one release-specific question: which actions must be impossible for ordinary contributors? There are three different actions that teams regularly collapse into the phrase “protect a tag”:
- Creating a new release tag such as
v2.4.0. - Moving an existing tag such as
v2.4.0to another commit. - Deleting an existing tag after a release or rollback.
A ruleset should make each decision explicit. That prevents an emergency-delete permission from silently becoming a permission to create every production tag.
2. Inventory patterns and the automation behind them
Do not migrate from memory. Create a short inventory for every repository that publishes releases, and include the workflow or person that creates tags. A pattern such as v* is not enough context: v* might cover stable releases, release candidates, and manually created test tags if the team has not named those categories carefully.
Use Git locally to identify the names currently in use, then compare them with release workflow triggers and package publication logic:
git fetch --tags --force
git tag --list 'v*'
git tag --list 'release-*'
git tag --list '*-rc*'
That command does not reveal authorization, but it exposes naming drift. If you expected only semantic-version tags and find v2.0.0-rc.1, v2.0.0-hotfix, and vnext, a single broad rule may have more consequences than its author intended.
| Inventory field | Example | Why it changes the ruleset |
|---|---|---|
| Legacy pattern | v* |
Defines the initial target candidate, not necessarily the final target. |
| Tag creator | Release GitHub Actions workflow | Determines the actor that needs a permitted path. |
| Delete operator | Release engineering maintainers | May need a narrower emergency bypass than the creator. |
| Consumer | Package publish workflow | Shows why an unexpected tag can have external effects. |
| Exceptions | v*-rc.* |
Often deserves a separate target and a different policy. |
3. Translate one protected-tag pattern into explicit rules
For each legacy pattern, write the replacement as a small policy statement before configuring the ruleset. For example: “Only the release automation path may create stable version tags; stable tags cannot be moved or deleted by normal repository collaborators.” This sentence is more useful than “protect v*” because it separates desired outcomes from a legacy feature label.
The exact control names in GitHub may evolve, but the migration mapping remains stable: target the relevant tags, then decide whether creations, updates, and deletions are restricted. Where the organization uses bypass actors, grant them deliberately rather than assuming repository write access should be sufficient.
| Legacy intent | Tag-targeting ruleset design | Migration question |
|---|---|---|
Protect all release tags matching v* |
Target tags matching v* |
Does that also catch prerelease names? |
| Prevent unauthorized new tags | Restrict tag creation | Which automation actor creates production tags? |
| Prevent force-moving a release tag | Restrict tag updates | Can anyone retag a published release during an incident? |
| Prevent release-tag deletion | Restrict tag deletions | Who can remove an accidental tag, and under what review? |
| Allow exceptional release work | Add a narrowly scoped bypass path | Is the bypass a person, a team, or automation? |
The tradeoff nobody mentions is operational: a ruleset that correctly prevents deletion also prevents a rushed correction unless the team has rehearsed its escalation path. Protecting tags is not the same as making mistakes disappear; it is choosing who can repair them.
4. Split stable releases from prereleases before targeting tags
Many teams begin with v* because it is easy to explain. It is also frequently too broad. If release candidates use names such as v3.1.0-rc.1, then a broad stable-release target may subject experimental release work to the same controls as immutable published versions.
Use names and targets that reflect the deployment contract. A practical arrangement is one ruleset for stable release tags and another for candidate tags. Stable tags receive creation, update, and deletion restrictions; candidates can have a less restrictive policy if the team genuinely needs to retag or delete them during testing.
Stable release family: v*
Prerelease family: v*-rc.*
Operational test family: test-*
The important work is deciding whether the first family actually excludes the second in your repository’s tag naming convention. Do not assume that a human reading “stable” will infer the same boundary that a pattern evaluates. Test proposed patterns against a representative list of existing names, including the oldest tags and odd historical exceptions.
If a repository cannot cleanly distinguish stable tags from candidates by name, fix the naming convention before building a complicated authorization policy. A ruleset cannot infer release intent from a commit message, release title, or package version; it evaluates the repository target you configure.
5. Work through a release repository end to end
Consider a repository with three tag classes: v2.7.0 for published releases, v2.8.0-rc.2 for release candidates, and test-2026-09-21 for a deployment rehearsal. The release workflow creates stable tags after tests pass. Maintainers may create or delete candidate and test tags during validation.
Start with two rulesets rather than one universal rule:
- Stable releases: target the stable version-tag pattern; restrict creation, updates, and deletions; permit only the designated release path to bypass when creation is required.
- Release candidates: target the candidate pattern; restrict creation if candidates must originate from automation, but do not automatically copy stable deletion and update restrictions.
Then walk through four real operations. The release workflow creates v2.7.0; that must succeed through the intended release permission. A maintainer attempts to force-push v2.7.0 to a different commit; that must fail. A maintainer deletes v2.8.0-rc.2 after a bad test build; whether that succeeds depends on the candidate policy, not on an assumption that all tags are immutable. Finally, a developer creates test-2026-09-21; it should remain outside release controls unless you have deliberately targeted it.
This exercise finds hidden dependencies quickly. If a workflow that publishes packages creates tags directly, its permission model is part of the ruleset design. If humans create tags from local clones, changing the ruleset changes their daily release procedure, not merely a repository setting.
6. Understand aggregation: overlapping rulesets do not override each other
GitHub documents that rulesets do not have a priority. When multiple rulesets target the same branch or tag, their rules aggregate. If the same rule is defined differently across the applicable rulesets, GitHub applies the most restrictive version. Rulesets also layer with protection rules that target the same branch or tag.
This has a counterintuitive consequence: do not model rulesets as ordered firewall rules where a later “release automation” ruleset overrides an earlier “protect releases” ruleset. It does not. A permissive second ruleset cannot relax a restrictive first ruleset for the same tag. If v2.7.0 matches two rulesets and either one restricts deletion, deletion stays restricted.
| Ruleset | Target | Configured restriction | Effect on v2.7.0 |
|---|---|---|---|
| Release tags | v* |
Restrict updates and deletions | Updates and deletions are restricted. |
| Stable tags | Stable-version pattern | Restrict creations | Creation is also restricted. |
| Candidate tags | Candidate pattern | Restrict deletions | Relevant only if the tag matches that target too. |
The policy outcome for v2.7.0 is the union of applicable restrictions: creation, update, and deletion can all be restricted even though no single ruleset lists all three. Aggregation usually makes a policy stricter, not broader. The “broader permission” accident appears when teams mistakenly believe a restrictive general rule covers a tag that it does not, or when they grant a very broad bypass to make an aggregated policy workable.
7. Keep bypasses narrower than the tag pattern
The common failure mode is not an overly permissive rule checkbox. It is a broad exception introduced after CI fails to create a release tag. Someone gives a large maintainer group a bypass, and that group can now perform the very update or deletion the ruleset was meant to prevent.
Apply a simple decision rule: if an actor only needs to create the next stable tag, do not give that actor a standing ability to rewrite or delete stable tags. Separate creation from repair operations in your review discussion, even if the eventual GitHub configuration groups permissions differently. The discussion forces the team to identify whether “release bot” is a workflow, a service identity, or a collection of people sharing credentials.
Record each bypass in the repository’s release runbook with three fields:
- The actor or team that can use it.
- The action it exists to support, such as creating a stable tag.
- The incident scenario that justifies a stronger action, such as deleting an accidental unpublished tag.
A bypass should have an owner. “Repository administrators” is an authorization category, not an accountable release process. If the only answer to a failed release is “ask an admin,” the migration has preserved technical protection while leaving operational access undefined.
8. Validate with safe tags and expected failures
Configuration review is not enough because the relevant question is behavioral: can the intended release path create a tag, and can an unintended path fail? Test in a non-production repository first, or use a deliberately non-release tag family that is covered by the same pattern logic without triggering publication workflows.
- Create a test tag as the intended release automation actor or through its normal workflow.
- Attempt to create the same protected-family tag as a normal collaborator.
- Attempt to move an existing protected tag to a different commit.
- Attempt to delete that tag.
- Repeat the four operations for a prerelease-style tag and verify it gets the intended, possibly different, policy.
Capture the result of every expected failure in the pull request or change record. A failure is evidence that the policy works; “we could not push the tag” should not be treated as an unexplained CI outage. Also inspect all rulesets that can target the test name. Because rules aggregate, changing one ruleset can alter a tag’s behavior even when the release ruleset itself was untouched.
If legacy protections still exist in a repository during transition, include them in the test explanation. GitHub’s documentation states that rulesets layer with protection rules targeting the same branch or tag, so a test result may reflect both systems rather than the new ruleset alone.
9. Complete this migration checklist this week
Pick one release repository rather than changing an organization’s repositories in bulk. A single repository gives you a concrete tag history, known workflows, and a release owner who can judge whether an emergency procedure is realistic.
- Export a list of current tags with
git tag --listand group them into stable, prerelease, and test families. - Write down every existing legacy protected-tag pattern and the person or automation that creates matching tags.
- For each family, decide separately whether creation, updates, and deletions must be restricted.
- Create tag-targeting rulesets whose names describe intent, such as “Stable release tags — immutable.”
- Check every overlapping target and calculate the aggregate restrictions for one representative tag.
- Document any bypass with an actor, purpose, and incident owner.
- Run creation, update, and deletion tests through both the release path and a normal collaborator path.
- Update the release runbook before the next production release, not after the first blocked tag push.
The lasting benefit of rulesets is not simply that GitHub has replaced an older settings page. It is that your repository can state, in reviewable pieces, who may create a release identity and who may change history after that identity exists. Keep patterns narrow, assume overlaps aggregate, and make exceptions as specific as the release action they are meant to enable.